UrlFetcherTests.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /* eslint-disable
  2. no-return-assign,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS102: Remove unnecessary code created because of implicit returns
  9. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  10. */
  11. const SandboxedModule = require('sandboxed-module')
  12. const sinon = require('sinon')
  13. const { expect } = require('chai')
  14. const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher')
  15. const { EventEmitter } = require('events')
  16. describe('UrlFetcher', function () {
  17. beforeEach(function () {
  18. this.callback = sinon.stub()
  19. this.url = 'https://www.example.com/file/here?query=string'
  20. return (this.UrlFetcher = SandboxedModule.require(modulePath, {
  21. requires: {
  22. request: {
  23. defaults: (this.defaults = sinon.stub().returns((this.request = {}))),
  24. },
  25. fs: (this.fs = {
  26. rename: sinon.stub().yields(),
  27. unlink: sinon.stub().yields(),
  28. }),
  29. '@overleaf/settings': (this.settings = {
  30. apis: {
  31. clsiPerf: {
  32. host: 'localhost:3043',
  33. },
  34. },
  35. }),
  36. },
  37. }))
  38. })
  39. describe('pipeUrlToFileWithRetry', function () {
  40. this.beforeEach(function () {
  41. this.UrlFetcher.pipeUrlToFile = sinon.stub()
  42. })
  43. it('should call pipeUrlToFile', function (done) {
  44. this.UrlFetcher.pipeUrlToFile.callsArgWith(2)
  45. this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => {
  46. expect(err).to.equal(undefined)
  47. this.UrlFetcher.pipeUrlToFile.called.should.equal(true)
  48. done()
  49. })
  50. })
  51. it('should call pipeUrlToFile multiple times on error', function (done) {
  52. const error = new Error("couldn't download file")
  53. this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error)
  54. this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => {
  55. expect(err).to.equal(error)
  56. this.UrlFetcher.pipeUrlToFile.callCount.should.equal(3)
  57. done()
  58. })
  59. })
  60. it('should call pipeUrlToFile twice if only 1 error', function (done) {
  61. this.UrlFetcher.pipeUrlToFile.onCall(0).callsArgWith(2, 'error')
  62. this.UrlFetcher.pipeUrlToFile.onCall(1).callsArgWith(2)
  63. this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => {
  64. expect(err).to.equal(undefined)
  65. this.UrlFetcher.pipeUrlToFile.callCount.should.equal(2)
  66. done()
  67. })
  68. })
  69. })
  70. describe('pipeUrlToFile', function () {
  71. it('should turn off the cookie jar in request', function () {
  72. return this.defaults.calledWith({ jar: false }).should.equal(true)
  73. })
  74. describe('rewrite url domain if filestoreDomainOveride is set', function () {
  75. beforeEach(function () {
  76. this.path = '/path/to/file/on/disk'
  77. this.request.get = sinon
  78. .stub()
  79. .returns((this.urlStream = new EventEmitter()))
  80. this.urlStream.pipe = sinon.stub()
  81. this.urlStream.pause = sinon.stub()
  82. this.urlStream.resume = sinon.stub()
  83. this.fs.createWriteStream = sinon
  84. .stub()
  85. .returns((this.fileStream = new EventEmitter()))
  86. return (this.fs.unlink = (file, callback) => callback())
  87. })
  88. it('should use the normal domain when override not set', function (done) {
  89. this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
  90. this.request.get.args[0][0].url.should.equal(this.url)
  91. return done()
  92. })
  93. this.res = { statusCode: 200 }
  94. this.urlStream.emit('response', this.res)
  95. this.urlStream.emit('end')
  96. return this.fileStream.emit('finish')
  97. })
  98. it('should not use override clsiPerf domain when filestoreDomainOveride is set', function (done) {
  99. this.settings.filestoreDomainOveride = '192.11.11.11'
  100. const url = 'http://localhost:3043/file/here?query=string'
  101. this.UrlFetcher.pipeUrlToFile(url, this.path, () => {
  102. this.request.get.args[0][0].url.should.equal(url)
  103. done()
  104. })
  105. this.res = { statusCode: 200 }
  106. this.urlStream.emit('response', this.res)
  107. this.urlStream.emit('end')
  108. this.fileStream.emit('finish')
  109. })
  110. return it('should use override domain when filestoreDomainOveride is set', function (done) {
  111. this.settings.filestoreDomainOveride = '192.11.11.11'
  112. this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
  113. this.request.get.args[0][0].url.should.equal(
  114. '192.11.11.11/file/here?query=string'
  115. )
  116. return done()
  117. })
  118. this.res = { statusCode: 200 }
  119. this.urlStream.emit('response', this.res)
  120. this.urlStream.emit('end')
  121. return this.fileStream.emit('finish')
  122. })
  123. })
  124. return describe('pipeUrlToFile', function () {
  125. beforeEach(function (done) {
  126. this.path = '/path/to/file/on/disk'
  127. this.request.get = sinon
  128. .stub()
  129. .returns((this.urlStream = new EventEmitter()))
  130. this.urlStream.pipe = sinon.stub()
  131. this.urlStream.pause = sinon.stub()
  132. this.urlStream.resume = sinon.stub()
  133. this.fs.createWriteStream = sinon
  134. .stub()
  135. .returns((this.fileStream = new EventEmitter()))
  136. this.fs.unlink = (file, callback) => callback()
  137. return done()
  138. })
  139. describe('successfully', function () {
  140. beforeEach(function (done) {
  141. this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
  142. this.callback()
  143. return done()
  144. })
  145. this.res = { statusCode: 200 }
  146. this.urlStream.emit('response', this.res)
  147. this.urlStream.emit('end')
  148. return this.fileStream.emit('finish')
  149. })
  150. it('should request the URL', function () {
  151. return this.request.get
  152. .calledWith(sinon.match({ url: this.url }))
  153. .should.equal(true)
  154. })
  155. it('should open the atomic file for writing', function () {
  156. return this.fs.createWriteStream
  157. .calledWith(this.path + '~')
  158. .should.equal(true)
  159. })
  160. it('should move the atomic file to the target', function () {
  161. return this.fs.rename
  162. .calledWith(this.path + '~', this.path)
  163. .should.equal(true)
  164. })
  165. it('should pipe the URL to the file', function () {
  166. return this.urlStream.pipe
  167. .calledWith(this.fileStream)
  168. .should.equal(true)
  169. })
  170. return it('should call the callback', function () {
  171. return this.callback.called.should.equal(true)
  172. })
  173. })
  174. describe('with non success status code', function () {
  175. beforeEach(function (done) {
  176. this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => {
  177. this.callback(err)
  178. return done()
  179. })
  180. this.res = { statusCode: 404 }
  181. this.urlStream.emit('response', this.res)
  182. return this.urlStream.emit('end')
  183. })
  184. it('should call the callback with an error', function () {
  185. this.callback.calledWith(sinon.match(Error)).should.equal(true)
  186. const message = this.callback.args[0][0].message
  187. expect(message).to.include(
  188. 'URL returned non-success status code: 404'
  189. )
  190. })
  191. })
  192. return describe('with error', function () {
  193. beforeEach(function (done) {
  194. this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => {
  195. this.callback(err)
  196. return done()
  197. })
  198. return this.urlStream.emit(
  199. 'error',
  200. (this.error = new Error('something went wrong'))
  201. )
  202. })
  203. it('should call the callback with the error', function () {
  204. return this.callback.calledWith(this.error).should.equal(true)
  205. })
  206. return it('should only call the callback once, even if end is called', function () {
  207. this.urlStream.emit('end')
  208. return this.callback.calledOnce.should.equal(true)
  209. })
  210. })
  211. })
  212. })
  213. })