Files
overleaf-cep/services/clsi/test/unit/js/ResourceStateManager.test.js
Andrew Rumble cd7da983d1 Merge pull request #30232 from overleaf/ar/convert-clsi-to-es-modules
[clsi] convert to ES modules

GitOrigin-RevId: fb7fa52cc8f678ee31be352e62a5dff95e88008b
2026-01-22 09:06:23 +00:00

230 lines
6.4 KiB
JavaScript

import { vi, expect, describe, beforeEach, it } from 'vitest'
import sinon from 'sinon'
import Path from 'node:path'
import * as Errors from '../../../app/js/Errors.js'
const modulePath = Path.join(
import.meta.dirname,
'../../../app/js/ResourceStateManager'
)
describe('ResourceStateManager', () => {
beforeEach(async ctx => {
vi.doMock('fs', () => ({
default: (ctx.fs = {}),
}))
vi.doMock('../../../app/js/SafeReader', () => ({
default: (ctx.SafeReader = {}),
}))
ctx.ResourceStateManager = (await import(modulePath)).default
ctx.basePath = '/path/to/write/files/to'
ctx.resources = [
{ path: 'resource-1-mock' },
{ path: 'resource-2-mock' },
{ path: 'resource-3-mock' },
]
ctx.state = '1234567890'
ctx.resourceFileName = `${ctx.basePath}/.project-sync-state`
ctx.resourceFileContents = `${ctx.resources[0].path}\n${ctx.resources[1].path}\n${ctx.resources[2].path}\nstateHash:${ctx.state}`
ctx.callback = sinon.stub()
})
describe('saveProjectState', () => {
beforeEach(ctx => {
ctx.fs.writeFile = sinon.stub().callsArg(2)
})
describe('when the state is specified', () => {
beforeEach(ctx => {
ctx.ResourceStateManager.saveProjectState(
ctx.state,
ctx.resources,
ctx.basePath,
ctx.callback
)
})
it('should write the resource list to disk', ctx => {
ctx.fs.writeFile
.calledWith(ctx.resourceFileName, ctx.resourceFileContents)
.should.equal(true)
})
it('should call the callback', ctx => {
ctx.callback.called.should.equal(true)
})
})
describe('when the state is undefined', () => {
beforeEach(ctx => {
ctx.state = undefined
ctx.fs.unlink = sinon.stub().callsArg(1)
ctx.ResourceStateManager.saveProjectState(
ctx.state,
ctx.resources,
ctx.basePath,
ctx.callback
)
})
it('should unlink the resource file', ctx => {
ctx.fs.unlink.calledWith(ctx.resourceFileName).should.equal(true)
})
it('should not write the resource list to disk', ctx => {
ctx.fs.writeFile.called.should.equal(false)
})
it('should call the callback', ctx => {
ctx.callback.called.should.equal(true)
})
})
})
describe('checkProjectStateMatches', () => {
describe('when the state matches', () => {
beforeEach(ctx => {
ctx.SafeReader.readFile = sinon
.stub()
.callsArgWith(3, null, ctx.resourceFileContents)
ctx.ResourceStateManager.checkProjectStateMatches(
ctx.state,
ctx.basePath,
ctx.callback
)
})
it('should read the resource file', ctx => {
ctx.SafeReader.readFile
.calledWith(ctx.resourceFileName)
.should.equal(true)
})
it('should call the callback with the results', ctx => {
ctx.callback.calledWithMatch(null, ctx.resources).should.equal(true)
})
})
describe('when the state file is not present', () => {
beforeEach(ctx => {
ctx.SafeReader.readFile = sinon.stub().callsArg(3)
ctx.ResourceStateManager.checkProjectStateMatches(
ctx.state,
ctx.basePath,
ctx.callback
)
})
it('should read the resource file', ctx => {
ctx.SafeReader.readFile
.calledWith(ctx.resourceFileName)
.should.equal(true)
})
it('should call the callback with an error', ctx => {
ctx.callback
.calledWith(sinon.match(Errors.FilesOutOfSyncError))
.should.equal(true)
const message = ctx.callback.args[0][0].message
expect(message).to.include('invalid state for incremental update')
})
})
describe('when the state does not match', () => {
beforeEach(ctx => {
ctx.SafeReader.readFile = sinon
.stub()
.callsArgWith(3, null, ctx.resourceFileContents)
ctx.ResourceStateManager.checkProjectStateMatches(
'not-the-original-state',
ctx.basePath,
ctx.callback
)
})
it('should call the callback with an error', ctx => {
ctx.callback
.calledWith(sinon.match(Errors.FilesOutOfSyncError))
.should.equal(true)
const message = ctx.callback.args[0][0].message
expect(message).to.include('invalid state for incremental update')
})
})
})
describe('checkResourceFiles', () => {
describe('when all the files are present', () => {
beforeEach(ctx => {
ctx.allFiles = [
ctx.resources[0].path,
ctx.resources[1].path,
ctx.resources[2].path,
]
ctx.ResourceStateManager.checkResourceFiles(
ctx.resources,
ctx.allFiles,
ctx.basePath,
ctx.callback
)
})
it('should call the callback', ctx => {
ctx.callback.calledWithExactly().should.equal(true)
})
})
describe('when there is a missing file', () => {
beforeEach(ctx => {
ctx.allFiles = [ctx.resources[0].path, ctx.resources[1].path]
ctx.fs.stat = sinon.stub().callsArgWith(1, new Error())
ctx.ResourceStateManager.checkResourceFiles(
ctx.resources,
ctx.allFiles,
ctx.basePath,
ctx.callback
)
})
it('should call the callback with an error', ctx => {
ctx.callback
.calledWith(sinon.match(Errors.FilesOutOfSyncError))
.should.equal(true)
const message = ctx.callback.args[0][0].message
expect(message).to.include(
'resource files missing in incremental update'
)
})
})
describe('when a resource contains a relative path', () => {
beforeEach(ctx => {
ctx.resources[0].path = '../foo/bar.tex'
ctx.allFiles = [
ctx.resources[0].path,
ctx.resources[1].path,
ctx.resources[2].path,
]
ctx.ResourceStateManager.checkResourceFiles(
ctx.resources,
ctx.allFiles,
ctx.basePath,
ctx.callback
)
})
it('should call the callback with an error', ctx => {
ctx.callback.calledWith(sinon.match(Error)).should.equal(true)
const message = ctx.callback.args[0][0].message
expect(message).to.include('relative path in resource file list')
})
})
})
})