FlushManagerTests.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import nock from 'nock'
  2. import { expect } from 'chai'
  3. import { fetchNothing, fetchJsonWithResponse } from '@overleaf/fetch-utils'
  4. import assert from 'node:assert'
  5. import mongodb from 'mongodb-legacy'
  6. import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js'
  7. import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js'
  8. import Settings from '@overleaf/settings'
  9. const { ObjectId } = mongodb
  10. const MockHistoryStore = () => nock('http://127.0.0.1:3100')
  11. const MockWeb = () => nock('http://127.0.0.1:3000')
  12. describe('Flushing old queues', function () {
  13. const historyId = new ObjectId().toString()
  14. beforeEach(async function () {
  15. this.timestamp = new Date()
  16. await ProjectHistoryApp.ensureRunning()
  17. this.projectId = new ObjectId().toString()
  18. this.docId = new ObjectId().toString()
  19. this.fileId = new ObjectId().toString()
  20. MockHistoryStore().post('/api/projects').reply(200, {
  21. projectId: historyId,
  22. })
  23. MockWeb()
  24. .get(`/project/${this.projectId}/details`)
  25. .reply(200, {
  26. name: 'Test Project',
  27. overleaf: {
  28. history: {
  29. id: historyId,
  30. },
  31. },
  32. })
  33. MockHistoryStore()
  34. .get(`/api/projects/${historyId}/latest/history`)
  35. .reply(200, {
  36. chunk: {
  37. startVersion: 0,
  38. history: {
  39. changes: [],
  40. },
  41. },
  42. })
  43. await ProjectHistoryClient.initializeProject(historyId)
  44. })
  45. afterEach(function () {
  46. nock.cleanAll()
  47. })
  48. describe('retrying an unflushed project', function () {
  49. describe('when the update is older than the cutoff', function () {
  50. beforeEach(async function () {
  51. this.flushCall = MockHistoryStore()
  52. .put(
  53. `/api/projects/${historyId}/blobs/0a207c060e61f3b88eaee0a8cd0696f46fb155eb`
  54. )
  55. .reply(201)
  56. .post(`/api/projects/${historyId}/legacy_changes?end_version=0`)
  57. .reply(200)
  58. const update = {
  59. pathname: '/main.tex',
  60. docLines: 'a\nb',
  61. doc: this.docId,
  62. meta: { user_id: this.user_id, ts: new Date() },
  63. }
  64. await ProjectHistoryClient.pushRawUpdate(this.projectId, update)
  65. await ProjectHistoryClient.setFirstOpTimestamp(
  66. this.projectId,
  67. Date.now() - 24 * 3600 * 1000
  68. )
  69. })
  70. it('flushes the project history queue', async function () {
  71. const response = await fetchNothing(
  72. 'http://127.0.0.1:3054/flush/old?maxAge=10800',
  73. {
  74. method: 'POST',
  75. }
  76. )
  77. expect(response.status).to.equal(200)
  78. assert(
  79. this.flushCall.isDone(),
  80. 'made calls to history service to store updates'
  81. )
  82. })
  83. it('flushes the project history queue in the background when requested', async function () {
  84. const { json, response } = await fetchJsonWithResponse(
  85. 'http://127.0.0.1:3054/flush/old?maxAge=10800&background=1',
  86. {
  87. method: 'POST',
  88. }
  89. )
  90. expect(response.status).to.equal(200)
  91. expect(json).to.deep.equal({
  92. message: 'running flush in background',
  93. })
  94. assert(
  95. !this.flushCall.isDone(),
  96. 'did not make calls to history service to store updates in the foreground'
  97. )
  98. await new Promise(resolve => setTimeout(resolve, 1000))
  99. assert(
  100. this.flushCall.isDone(),
  101. 'made calls to history service to store updates in the background'
  102. )
  103. })
  104. })
  105. describe('when the update is newer than the cutoff', function () {
  106. beforeEach(async function () {
  107. this.flushCall = MockHistoryStore()
  108. .put(
  109. `/api/projects/${historyId}/blobs/0a207c060e61f3b88eaee0a8cd0696f46fb155eb`
  110. )
  111. .reply(201)
  112. .post(`/api/projects/${historyId}/legacy_changes?end_version=0`)
  113. .reply(200)
  114. const update = {
  115. pathname: '/main.tex',
  116. docLines: 'a\nb',
  117. doc: this.docId,
  118. meta: { user_id: this.user_id, ts: new Date() },
  119. }
  120. await ProjectHistoryClient.pushRawUpdate(this.projectId, update)
  121. await ProjectHistoryClient.setFirstOpTimestamp(
  122. this.projectId,
  123. Date.now() - 60 * 1000
  124. )
  125. })
  126. it('does not flush the project history queue', async function () {
  127. const response = await fetchNothing(
  128. `http://127.0.0.1:3054/flush/old?maxAge=${3 * 3600}`,
  129. {
  130. method: 'POST',
  131. }
  132. )
  133. expect(response.status).to.equal(200)
  134. assert(
  135. !this.flushCall.isDone(),
  136. 'did not make calls to history service to store updates'
  137. )
  138. })
  139. })
  140. describe('when the update is newer than the cutoff and project has short queue', function () {
  141. beforeEach(function () {
  142. Settings.shortHistoryQueues.push(this.projectId)
  143. })
  144. afterEach(function () {
  145. Settings.shortHistoryQueues.length = 0
  146. })
  147. beforeEach(async function () {
  148. this.flushCall = MockHistoryStore()
  149. .put(
  150. `/api/projects/${historyId}/blobs/0a207c060e61f3b88eaee0a8cd0696f46fb155eb`
  151. )
  152. .reply(201)
  153. .post(`/api/projects/${historyId}/legacy_changes?end_version=0`)
  154. .reply(200)
  155. const update = {
  156. pathname: '/main.tex',
  157. docLines: 'a\nb',
  158. doc: this.docId,
  159. meta: { user_id: this.user_id, ts: new Date() },
  160. }
  161. await ProjectHistoryClient.pushRawUpdate(this.projectId, update)
  162. await ProjectHistoryClient.setFirstOpTimestamp(
  163. this.projectId,
  164. Date.now() - 60 * 1000
  165. )
  166. })
  167. it('flushes the project history queue', async function () {
  168. const response = await fetchNothing(
  169. `http://127.0.0.1:3054/flush/old?maxAge=${3 * 3600}`,
  170. {
  171. method: 'POST',
  172. }
  173. )
  174. expect(response.status).to.equal(200)
  175. assert(
  176. this.flushCall.isDone(),
  177. 'made calls to history service to store updates'
  178. )
  179. })
  180. it('flushes the project history queue in the background when requested', async function () {
  181. const { json, response } = await fetchJsonWithResponse(
  182. `http://127.0.0.1:3054/flush/old?maxAge=${3 * 3600}&background=1`,
  183. {
  184. method: 'POST',
  185. }
  186. )
  187. expect(response.status).to.equal(200)
  188. expect(json).to.deep.equal({
  189. message: 'running flush in background',
  190. })
  191. assert(
  192. !this.flushCall.isDone(),
  193. 'did not make calls to history service to store updates in the foreground'
  194. )
  195. await new Promise(resolve => setTimeout(resolve, 1000))
  196. assert(
  197. this.flushCall.isDone(),
  198. 'made calls to history service to store updates in the background'
  199. )
  200. })
  201. })
  202. describe('when the update does not have a timestamp', function () {
  203. beforeEach(async function () {
  204. this.flushCall = MockHistoryStore()
  205. .put(
  206. `/api/projects/${historyId}/blobs/0a207c060e61f3b88eaee0a8cd0696f46fb155eb`
  207. )
  208. .reply(201)
  209. .post(`/api/projects/${historyId}/legacy_changes?end_version=0`)
  210. .reply(200)
  211. const update = {
  212. pathname: '/main.tex',
  213. docLines: 'a\nb',
  214. doc: this.docId,
  215. meta: { user_id: this.user_id, ts: new Date() },
  216. }
  217. this.startDate = Date.now()
  218. await ProjectHistoryClient.pushRawUpdate(this.projectId, update)
  219. await ProjectHistoryClient.clearFirstOpTimestamp(this.projectId)
  220. })
  221. it('flushes the project history queue anyway', async function () {
  222. const response = await fetchNothing(
  223. `http://127.0.0.1:3054/flush/old?maxAge=${3 * 3600}`,
  224. {
  225. method: 'POST',
  226. }
  227. )
  228. expect(response.status).to.equal(200)
  229. assert(
  230. this.flushCall.isDone(),
  231. 'made calls to history service to store updates'
  232. )
  233. const result = await ProjectHistoryClient.getFirstOpTimestamp(
  234. this.projectId
  235. )
  236. expect(result).to.be.null
  237. })
  238. })
  239. })
  240. })