FlushManagerTests.js 7.0 KB

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