HistoryTests.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /* eslint-disable
  2. camelcase,
  3. max-len,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const { expect } = require('chai')
  15. const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
  16. const User = require('./helpers/User')
  17. const MockV1HistoryApiClass = require('./mocks/MockV1HistoryApi')
  18. let MockV1HistoryApi
  19. before(function () {
  20. MockV1HistoryApi = MockV1HistoryApiClass.instance()
  21. })
  22. describe('History', function () {
  23. beforeEach(function (done) {
  24. this.owner = new User()
  25. return this.owner.login(done)
  26. })
  27. describe('zip download of version', function () {
  28. it('should stream the zip file of a version', function (done) {
  29. return this.owner.createProject(
  30. 'example-project',
  31. (error, project_id) => {
  32. this.project_id = project_id
  33. if (error != null) {
  34. return done(error)
  35. }
  36. this.v1_history_id = 42
  37. return db.projects.updateOne(
  38. {
  39. _id: ObjectId(this.project_id),
  40. },
  41. {
  42. $set: {
  43. 'overleaf.history.id': this.v1_history_id,
  44. },
  45. },
  46. error => {
  47. if (error != null) {
  48. return done(error)
  49. }
  50. return this.owner.request(
  51. `/project/${this.project_id}/version/42/zip`,
  52. (error, response, body) => {
  53. if (error != null) {
  54. return done(error)
  55. }
  56. expect(response.statusCode).to.equal(200)
  57. expect(response.headers['content-type']).to.equal(
  58. 'application/zip'
  59. )
  60. expect(response.headers['content-disposition']).to.equal(
  61. 'attachment; filename="example-project%20(Version%2042).zip"'
  62. )
  63. expect(body).to.equal(
  64. `Mock zip for ${this.v1_history_id} at version 42`
  65. )
  66. return done()
  67. }
  68. )
  69. }
  70. )
  71. }
  72. )
  73. })
  74. describe('request abort', function () {
  75. // Optional manual verification: add unique logging statements into
  76. // HistoryController._pipeHistoryZipToResponse
  77. // in each of the `req.aborted` branches and confirm that each branch
  78. // was covered.
  79. beforeEach(function setupNewProject(done) {
  80. this.owner.createProject('example-project', (error, project_id) => {
  81. this.project_id = project_id
  82. if (error) {
  83. return done(error)
  84. }
  85. this.v1_history_id = 42
  86. db.projects.updateOne(
  87. { _id: ObjectId(this.project_id) },
  88. {
  89. $set: {
  90. 'overleaf.history.id': this.v1_history_id,
  91. },
  92. },
  93. done
  94. )
  95. })
  96. })
  97. it('should abort the upstream request', function (done) {
  98. const request = this.owner.request(
  99. `/project/${this.project_id}/version/100/zip`
  100. )
  101. request.on('error', done)
  102. request.on('response', response => {
  103. expect(response.statusCode).to.equal(200)
  104. let receivedChunks = 0
  105. response.on('data', () => {
  106. receivedChunks++
  107. })
  108. response.resume()
  109. setTimeout(() => {
  110. request.abort()
  111. const receivedSoFar = receivedChunks
  112. const sentSoFar = MockV1HistoryApi.sentChunks
  113. // Ihe next assertions should verify that chunks are emitted
  114. // and received -- the exact number is not important.
  115. // In theory we are now emitting the 3rd chunk,
  116. // so this should be exactly 3, to not make this
  117. // test flaky, we allow +- 2 chunks.
  118. expect(sentSoFar).to.be.within(1, 4)
  119. expect(receivedSoFar).to.be.within(1, 4)
  120. setTimeout(() => {
  121. // The fake-s3 service should have stopped emitting chunks.
  122. // Ff not, that would be +5 in an ideal world (1 every 100ms).
  123. // On the happy-path (it stopped) it emitted +1 which was
  124. // in-flight and another +1 before it received the abort.
  125. expect(MockV1HistoryApi.sentChunks).to.be.below(sentSoFar + 5)
  126. expect(MockV1HistoryApi.sentChunks).to.be.within(
  127. sentSoFar,
  128. sentSoFar + 2
  129. )
  130. done()
  131. }, 500)
  132. }, 200)
  133. })
  134. })
  135. it('should skip the v1-history request', function (done) {
  136. const request = this.owner.request(
  137. `/project/${this.project_id}/version/100/zip`
  138. )
  139. setTimeout(() => {
  140. // This is a race-condition to abort the request after the
  141. // processing of all the the express middleware completed.
  142. // In case we abort before they complete, we do not hit our
  143. // abort logic, but express internal logic, which is OK.
  144. request.abort()
  145. }, 2)
  146. request.on('error', done)
  147. setTimeout(() => {
  148. expect(MockV1HistoryApi.requestedZipPacks).to.equal(0)
  149. done()
  150. }, 500)
  151. })
  152. it('should skip the async-polling', function (done) {
  153. const request = this.owner.request(
  154. `/project/${this.project_id}/version/100/zip`
  155. )
  156. MockV1HistoryApi.events.on('v1-history-pack-zip', () => {
  157. request.abort()
  158. })
  159. request.on('error', done)
  160. setTimeout(() => {
  161. expect(MockV1HistoryApi.fakeZipCall).to.equal(0)
  162. done()
  163. }, 3000) // initial polling delay is 2s
  164. })
  165. it('should skip the upstream request', function (done) {
  166. const request = this.owner.request(
  167. `/project/${this.project_id}/version/100/zip`
  168. )
  169. MockV1HistoryApi.events.on('v1-history-pack-zip', () => {
  170. setTimeout(() => {
  171. request.abort()
  172. }, 1000)
  173. })
  174. request.on('error', done)
  175. setTimeout(() => {
  176. expect(MockV1HistoryApi.fakeZipCall).to.equal(0)
  177. done()
  178. }, 3000) // initial polling delay is 2s
  179. })
  180. })
  181. it('should return 402 for non-v2-history project', function (done) {
  182. return this.owner.createProject('non-v2-project', (error, project_id) => {
  183. this.project_id = project_id
  184. if (error != null) {
  185. return done(error)
  186. }
  187. return db.projects.updateOne(
  188. {
  189. _id: ObjectId(this.project_id),
  190. },
  191. {
  192. $unset: {
  193. 'overleaf.history.id': true,
  194. },
  195. },
  196. error => {
  197. if (error != null) {
  198. return done(error)
  199. }
  200. return this.owner.request(
  201. `/project/${this.project_id}/version/42/zip`,
  202. (error, response, body) => {
  203. if (error != null) {
  204. return done(error)
  205. }
  206. expect(response.statusCode).to.equal(402)
  207. return done()
  208. }
  209. )
  210. }
  211. )
  212. })
  213. })
  214. })
  215. })