RetryTests.js 5.5 KB

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