FlushManagerTests.js 9.5 KB

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