ResourceStateManager.test.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import { vi, expect, describe, beforeEach, it } from 'vitest'
  2. import sinon from 'sinon'
  3. import Path from 'node:path'
  4. import * as Errors from '../../../app/js/Errors.js'
  5. const modulePath = Path.join(
  6. import.meta.dirname,
  7. '../../../app/js/ResourceStateManager'
  8. )
  9. describe('ResourceStateManager', () => {
  10. beforeEach(async ctx => {
  11. vi.doMock('fs', () => ({
  12. default: (ctx.fs = {}),
  13. }))
  14. vi.doMock('../../../app/js/SafeReader', () => ({
  15. default: (ctx.SafeReader = {}),
  16. }))
  17. ctx.ResourceStateManager = (await import(modulePath)).default
  18. ctx.basePath = '/path/to/write/files/to'
  19. ctx.resources = [
  20. { path: 'resource-1-mock' },
  21. { path: 'resource-2-mock' },
  22. { path: 'resource-3-mock' },
  23. ]
  24. ctx.state = '1234567890'
  25. ctx.resourceFileName = `${ctx.basePath}/.project-sync-state`
  26. ctx.resourceFileContents = `${ctx.resources[0].path}\n${ctx.resources[1].path}\n${ctx.resources[2].path}\nstateHash:${ctx.state}`
  27. ctx.callback = sinon.stub()
  28. })
  29. describe('saveProjectState', () => {
  30. beforeEach(ctx => {
  31. ctx.fs.writeFile = sinon.stub().callsArg(2)
  32. })
  33. describe('when the state is specified', () => {
  34. beforeEach(ctx => {
  35. ctx.ResourceStateManager.saveProjectState(
  36. ctx.state,
  37. ctx.resources,
  38. ctx.basePath,
  39. ctx.callback
  40. )
  41. })
  42. it('should write the resource list to disk', ctx => {
  43. ctx.fs.writeFile
  44. .calledWith(ctx.resourceFileName, ctx.resourceFileContents)
  45. .should.equal(true)
  46. })
  47. it('should call the callback', ctx => {
  48. ctx.callback.called.should.equal(true)
  49. })
  50. })
  51. describe('when the state is undefined', () => {
  52. beforeEach(ctx => {
  53. ctx.state = undefined
  54. ctx.fs.unlink = sinon.stub().callsArg(1)
  55. ctx.ResourceStateManager.saveProjectState(
  56. ctx.state,
  57. ctx.resources,
  58. ctx.basePath,
  59. ctx.callback
  60. )
  61. })
  62. it('should unlink the resource file', ctx => {
  63. ctx.fs.unlink.calledWith(ctx.resourceFileName).should.equal(true)
  64. })
  65. it('should not write the resource list to disk', ctx => {
  66. ctx.fs.writeFile.called.should.equal(false)
  67. })
  68. it('should call the callback', ctx => {
  69. ctx.callback.called.should.equal(true)
  70. })
  71. })
  72. })
  73. describe('checkProjectStateMatches', () => {
  74. describe('when the state matches', () => {
  75. beforeEach(ctx => {
  76. ctx.SafeReader.readFile = sinon
  77. .stub()
  78. .callsArgWith(3, null, ctx.resourceFileContents)
  79. ctx.ResourceStateManager.checkProjectStateMatches(
  80. ctx.state,
  81. ctx.basePath,
  82. ctx.callback
  83. )
  84. })
  85. it('should read the resource file', ctx => {
  86. ctx.SafeReader.readFile
  87. .calledWith(ctx.resourceFileName)
  88. .should.equal(true)
  89. })
  90. it('should call the callback with the results', ctx => {
  91. ctx.callback.calledWithMatch(null, ctx.resources).should.equal(true)
  92. })
  93. })
  94. describe('when the state file is not present', () => {
  95. beforeEach(ctx => {
  96. ctx.SafeReader.readFile = sinon.stub().callsArg(3)
  97. ctx.ResourceStateManager.checkProjectStateMatches(
  98. ctx.state,
  99. ctx.basePath,
  100. ctx.callback
  101. )
  102. })
  103. it('should read the resource file', ctx => {
  104. ctx.SafeReader.readFile
  105. .calledWith(ctx.resourceFileName)
  106. .should.equal(true)
  107. })
  108. it('should call the callback with an error', ctx => {
  109. ctx.callback
  110. .calledWith(sinon.match(Errors.FilesOutOfSyncError))
  111. .should.equal(true)
  112. const message = ctx.callback.args[0][0].message
  113. expect(message).to.include('invalid state for incremental update')
  114. })
  115. })
  116. describe('when the state does not match', () => {
  117. beforeEach(ctx => {
  118. ctx.SafeReader.readFile = sinon
  119. .stub()
  120. .callsArgWith(3, null, ctx.resourceFileContents)
  121. ctx.ResourceStateManager.checkProjectStateMatches(
  122. 'not-the-original-state',
  123. ctx.basePath,
  124. ctx.callback
  125. )
  126. })
  127. it('should call the callback with an error', ctx => {
  128. ctx.callback
  129. .calledWith(sinon.match(Errors.FilesOutOfSyncError))
  130. .should.equal(true)
  131. const message = ctx.callback.args[0][0].message
  132. expect(message).to.include('invalid state for incremental update')
  133. })
  134. })
  135. })
  136. describe('checkResourceFiles', () => {
  137. describe('when all the files are present', () => {
  138. beforeEach(ctx => {
  139. ctx.allFiles = [
  140. ctx.resources[0].path,
  141. ctx.resources[1].path,
  142. ctx.resources[2].path,
  143. ]
  144. ctx.ResourceStateManager.checkResourceFiles(
  145. ctx.resources,
  146. ctx.allFiles,
  147. ctx.basePath,
  148. ctx.callback
  149. )
  150. })
  151. it('should call the callback', ctx => {
  152. ctx.callback.calledWithExactly().should.equal(true)
  153. })
  154. })
  155. describe('when there is a missing file', () => {
  156. beforeEach(ctx => {
  157. ctx.allFiles = [ctx.resources[0].path, ctx.resources[1].path]
  158. ctx.fs.stat = sinon.stub().callsArgWith(1, new Error())
  159. ctx.ResourceStateManager.checkResourceFiles(
  160. ctx.resources,
  161. ctx.allFiles,
  162. ctx.basePath,
  163. ctx.callback
  164. )
  165. })
  166. it('should call the callback with an error', ctx => {
  167. ctx.callback
  168. .calledWith(sinon.match(Errors.FilesOutOfSyncError))
  169. .should.equal(true)
  170. const message = ctx.callback.args[0][0].message
  171. expect(message).to.include(
  172. 'resource files missing in incremental update'
  173. )
  174. })
  175. })
  176. describe('when a resource contains a relative path', () => {
  177. beforeEach(ctx => {
  178. ctx.resources[0].path = '../foo/bar.tex'
  179. ctx.allFiles = [
  180. ctx.resources[0].path,
  181. ctx.resources[1].path,
  182. ctx.resources[2].path,
  183. ]
  184. ctx.ResourceStateManager.checkResourceFiles(
  185. ctx.resources,
  186. ctx.allFiles,
  187. ctx.basePath,
  188. ctx.callback
  189. )
  190. })
  191. it('should call the callback with an error', ctx => {
  192. ctx.callback.calledWith(sinon.match(Error)).should.equal(true)
  193. const message = ctx.callback.args[0][0].message
  194. expect(message).to.include('relative path in resource file list')
  195. })
  196. })
  197. })
  198. })