RetryTests.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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://127.0.0.1:3100')
  11. const MockWeb = () => nock('http://127.0.0.1:3000')
  12. const MockCallback = () => nock('http://127.0.0.1')
  13. describe('Retrying failed projects', 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.project_id = new ObjectId().toString()
  22. this.doc_id = new ObjectId().toString()
  23. this.file_id = new ObjectId().toString()
  24. MockHistoryStore().post('/api/projects').reply(200, {
  25. projectId: historyId,
  26. })
  27. MockWeb()
  28. .get(`/project/${this.project_id}/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 project history', function () {
  54. describe('when there is a soft failure', 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.doc_id,
  67. meta: { user_id: this.user_id, ts: new Date() },
  68. }
  69. async.series(
  70. [
  71. cb =>
  72. ProjectHistoryClient.pushRawUpdate(this.project_id, update, cb),
  73. cb =>
  74. ProjectHistoryClient.setFailure(
  75. {
  76. project_id: this.project_id,
  77. attempts: 1,
  78. error: 'soft-error',
  79. },
  80. cb
  81. ),
  82. ],
  83. done
  84. )
  85. })
  86. it('flushes the project history queue', function (done) {
  87. request.post(
  88. {
  89. url: 'http://127.0.0.1:3054/retry/failures?failureType=soft&limit=1&timeout=10000',
  90. },
  91. (error, res, body) => {
  92. if (error) {
  93. return done(error)
  94. }
  95. expect(res.statusCode).to.equal(200)
  96. assert(
  97. this.flushCall.isDone(),
  98. 'made calls to history service to store updates'
  99. )
  100. done()
  101. }
  102. )
  103. })
  104. it('retries in the background when requested', function (done) {
  105. this.callback = MockCallback()
  106. .matchHeader('Authorization', '123')
  107. .get('/ping')
  108. .reply(200)
  109. request.post(
  110. {
  111. url: 'http://127.0.0.1:3054/retry/failures?failureType=soft&limit=1&timeout=10000&callbackUrl=http%3A%2F%2F127.0.0.1%2Fping',
  112. headers: {
  113. 'X-CALLBACK-Authorization': '123',
  114. },
  115. },
  116. (error, res, body) => {
  117. if (error) {
  118. return done(error)
  119. }
  120. expect(res.statusCode).to.equal(200)
  121. expect(body).to.equal(
  122. '{"retryStatus":"running retryFailures in background"}'
  123. )
  124. assert(
  125. !this.flushCall.isDone(),
  126. 'did not make calls to history service to store updates in the foreground'
  127. )
  128. setTimeout(() => {
  129. assert(
  130. this.flushCall.isDone(),
  131. 'made calls to history service to store updates in the background'
  132. )
  133. assert(this.callback.isDone(), 'hit the callback url')
  134. done()
  135. }, 100)
  136. }
  137. )
  138. })
  139. })
  140. describe('when there is a hard failure', function () {
  141. beforeEach(function (done) {
  142. MockWeb()
  143. .get(`/project/${this.project_id}/details`)
  144. .reply(200, {
  145. name: 'Test Project',
  146. overleaf: {
  147. history: {
  148. id: historyId,
  149. },
  150. },
  151. })
  152. ProjectHistoryClient.setFailure(
  153. {
  154. project_id: this.project_id,
  155. attempts: 100,
  156. error: 'hard-error',
  157. },
  158. done
  159. )
  160. })
  161. it('calls web to resync the project', function (done) {
  162. const resyncCall = MockWeb()
  163. .post(`/project/${this.project_id}/history/resync`)
  164. .reply(200)
  165. request.post(
  166. {
  167. url: 'http://127.0.0.1:3054/retry/failures?failureType=hard&limit=1&timeout=10000',
  168. },
  169. (error, res, body) => {
  170. if (error) {
  171. return done(error)
  172. }
  173. expect(res.statusCode).to.equal(200)
  174. assert(resyncCall.isDone(), 'made a call to web to resync project')
  175. done()
  176. }
  177. )
  178. })
  179. })
  180. })
  181. })