HistoryTests.js 7.3 KB

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