TpdsProjectFlusher.test.mjs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { vi, expect } from 'vitest'
  2. import sinon from 'sinon'
  3. import mongodb from 'mongodb-legacy'
  4. import indirectlyImportModels from '../helpers/indirectlyImportModels.js'
  5. const { Project } = indirectlyImportModels(['Project'])
  6. const { ObjectId } = mongodb
  7. const MODULE_PATH =
  8. '../../../../app/src/Features/ThirdPartyDataStore/TpdsProjectFlusher'
  9. describe('TpdsProjectFlusher', function () {
  10. beforeEach(async function (ctx) {
  11. ctx.project = { _id: new ObjectId(), overleaf: { history: { id: 42 } } }
  12. ctx.folder = { _id: new ObjectId() }
  13. ctx.docs = {
  14. '/doc/one': {
  15. _id: 'mock-doc-1',
  16. lines: ['one'],
  17. rev: 5,
  18. folder: ctx.folder,
  19. },
  20. '/doc/two': {
  21. _id: 'mock-doc-2',
  22. lines: ['two'],
  23. rev: 6,
  24. folder: ctx.folder,
  25. },
  26. }
  27. ctx.files = {
  28. '/file/one': {
  29. _id: 'mock-file-1',
  30. rev: 7,
  31. folder: ctx.folder,
  32. hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  33. },
  34. '/file/two': {
  35. _id: 'mock-file-2',
  36. rev: 8,
  37. folder: ctx.folder,
  38. hash: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
  39. },
  40. }
  41. ctx.DocumentUpdaterHandler = {
  42. promises: {
  43. flushProjectToMongo: sinon.stub().resolves(),
  44. },
  45. }
  46. ctx.ProjectGetter = {
  47. promises: {
  48. getProject: sinon.stub().resolves(ctx.project),
  49. },
  50. }
  51. ctx.ProjectEntityHandler = {
  52. promises: {
  53. getAllDocs: sinon.stub().withArgs(ctx.project._id).resolves(ctx.docs),
  54. getAllFiles: sinon.stub().withArgs(ctx.project._id).resolves(ctx.files),
  55. },
  56. }
  57. ctx.TpdsUpdateSender = {
  58. promises: {
  59. addDoc: sinon.stub().resolves(),
  60. addFile: sinon.stub().resolves(),
  61. },
  62. }
  63. ctx.ProjectMock = sinon.mock(Project)
  64. vi.doMock(
  65. '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
  66. () => ({
  67. default: ctx.DocumentUpdaterHandler,
  68. })
  69. )
  70. vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
  71. default: ctx.ProjectGetter,
  72. }))
  73. vi.doMock(
  74. '../../../../app/src/Features/Project/ProjectEntityHandler',
  75. () => ({
  76. default: ctx.ProjectEntityHandler,
  77. })
  78. )
  79. vi.doMock('../../../../app/src/models/Project', () => ({
  80. Project,
  81. }))
  82. vi.doMock(
  83. '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender',
  84. () => ({
  85. default: ctx.TpdsUpdateSender,
  86. })
  87. )
  88. ctx.TpdsProjectFlusher = (await import(MODULE_PATH)).default
  89. })
  90. afterEach(function (ctx) {
  91. ctx.ProjectMock.restore()
  92. })
  93. describe('flushProjectToTpds', function () {
  94. describe('usually', function () {
  95. beforeEach(async function (ctx) {
  96. await ctx.TpdsProjectFlusher.promises.flushProjectToTpds(
  97. ctx.project._id
  98. )
  99. })
  100. it('should flush the project from the doc updater', function (ctx) {
  101. expect(
  102. ctx.DocumentUpdaterHandler.promises.flushProjectToMongo
  103. ).to.have.been.calledWith(ctx.project._id)
  104. })
  105. it('should flush each doc to the TPDS', function (ctx) {
  106. for (const [path, doc] of Object.entries(ctx.docs)) {
  107. expect(ctx.TpdsUpdateSender.promises.addDoc).to.have.been.calledWith({
  108. projectId: ctx.project._id,
  109. docId: doc._id,
  110. projectName: ctx.project.name,
  111. rev: doc.rev,
  112. path,
  113. folderId: ctx.folder._id,
  114. })
  115. }
  116. })
  117. it('should flush each file to the TPDS', function (ctx) {
  118. for (const [path, file] of Object.entries(ctx.files)) {
  119. expect(ctx.TpdsUpdateSender.promises.addFile).to.have.been.calledWith(
  120. {
  121. projectId: ctx.project._id,
  122. historyId: ctx.project.overleaf.history.id,
  123. fileId: file._id,
  124. hash: file.hash,
  125. projectName: ctx.project.name,
  126. rev: file.rev,
  127. path,
  128. folderId: ctx.folder._id,
  129. }
  130. )
  131. }
  132. })
  133. })
  134. describe('when a TPDS flush is pending', function () {
  135. beforeEach(async function (ctx) {
  136. ctx.project.deferredTpdsFlushCounter = 2
  137. ctx.ProjectMock.expects('updateOne')
  138. .withArgs(
  139. {
  140. _id: ctx.project._id,
  141. deferredTpdsFlushCounter: { $lte: 2 },
  142. },
  143. { $set: { deferredTpdsFlushCounter: 0 } }
  144. )
  145. .chain('exec')
  146. .resolves()
  147. await ctx.TpdsProjectFlusher.promises.flushProjectToTpds(
  148. ctx.project._id
  149. )
  150. })
  151. it('resets the deferred flush counter', function (ctx) {
  152. ctx.ProjectMock.verify()
  153. })
  154. })
  155. })
  156. describe('deferProjectFlushToTpds', function () {
  157. beforeEach(async function (ctx) {
  158. ctx.ProjectMock.expects('updateOne')
  159. .withArgs(
  160. {
  161. _id: ctx.project._id,
  162. },
  163. { $inc: { deferredTpdsFlushCounter: 1 } }
  164. )
  165. .chain('exec')
  166. .resolves()
  167. await ctx.TpdsProjectFlusher.promises.deferProjectFlushToTpds(
  168. ctx.project._id
  169. )
  170. })
  171. it('increments the deferred flush counter', function (ctx) {
  172. ctx.ProjectMock.verify()
  173. })
  174. })
  175. describe('flushProjectToTpdsIfNeeded', function () {
  176. let cases = [0, undefined]
  177. cases.forEach(counterValue => {
  178. describe(`when the deferred flush counter is ${counterValue}`, function () {
  179. beforeEach(async function (ctx) {
  180. ctx.project.deferredTpdsFlushCounter = counterValue
  181. await ctx.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
  182. ctx.project._id
  183. )
  184. })
  185. it("doesn't flush the project from the doc updater", function (ctx) {
  186. expect(ctx.DocumentUpdaterHandler.promises.flushProjectToMongo).not.to
  187. .have.been.called
  188. })
  189. it("doesn't flush any doc", function (ctx) {
  190. expect(ctx.TpdsUpdateSender.promises.addDoc).not.to.have.been.called
  191. })
  192. it("doesn't flush any file", function (ctx) {
  193. expect(ctx.TpdsUpdateSender.promises.addFile).not.to.have.been.called
  194. })
  195. })
  196. })
  197. cases = [1, 2]
  198. cases.forEach(counterValue => {
  199. describe(`when the deferred flush counter is ${counterValue}`, function () {
  200. beforeEach(async function (ctx) {
  201. ctx.project.deferredTpdsFlushCounter = counterValue
  202. ctx.ProjectMock.expects('updateOne')
  203. .withArgs(
  204. {
  205. _id: ctx.project._id,
  206. deferredTpdsFlushCounter: { $lte: counterValue },
  207. },
  208. { $set: { deferredTpdsFlushCounter: 0 } }
  209. )
  210. .chain('exec')
  211. .resolves()
  212. await ctx.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
  213. ctx.project._id
  214. )
  215. })
  216. it('flushes the project from the doc updater', function (ctx) {
  217. expect(
  218. ctx.DocumentUpdaterHandler.promises.flushProjectToMongo
  219. ).to.have.been.calledWith(ctx.project._id)
  220. })
  221. it('flushes each doc to the TPDS', function (ctx) {
  222. for (const [path, doc] of Object.entries(ctx.docs)) {
  223. expect(
  224. ctx.TpdsUpdateSender.promises.addDoc
  225. ).to.have.been.calledWith({
  226. projectId: ctx.project._id,
  227. docId: doc._id,
  228. projectName: ctx.project.name,
  229. rev: doc.rev,
  230. path,
  231. folderId: ctx.folder._id,
  232. })
  233. }
  234. })
  235. it('flushes each file to the TPDS', function (ctx) {
  236. for (const [path, file] of Object.entries(ctx.files)) {
  237. expect(
  238. ctx.TpdsUpdateSender.promises.addFile
  239. ).to.have.been.calledWith({
  240. projectId: ctx.project._id,
  241. historyId: ctx.project.overleaf.history.id,
  242. fileId: file._id,
  243. hash: file.hash,
  244. projectName: ctx.project.name,
  245. rev: file.rev,
  246. path,
  247. folderId: ctx.folder._id,
  248. })
  249. }
  250. })
  251. it('resets the deferred flush counter', function (ctx) {
  252. ctx.ProjectMock.verify()
  253. })
  254. })
  255. })
  256. })
  257. })