uploader.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. 'use strict'
  2. jest.mock('tus-js-client')
  3. const intoStream = require('into-stream')
  4. const fs = require('fs')
  5. const nock = require('nock')
  6. const Uploader = require('../../src/server/Uploader')
  7. const socketClient = require('../mocksocket')
  8. const standalone = require('../../src/standalone')
  9. afterAll(() => {
  10. nock.cleanAll()
  11. nock.restore()
  12. })
  13. process.env.COMPANION_DATADIR = './test/output'
  14. process.env.COMPANION_DOMAIN = 'localhost:3020'
  15. const { companionOptions } = standalone()
  16. describe('uploader with tus protocol', () => {
  17. test('uploader respects uploadUrls', async () => {
  18. const opts = {
  19. endpoint: 'http://localhost/files',
  20. companionOptions: { ...companionOptions, uploadUrls: [/^http:\/\/url.myendpoint.com\//] },
  21. }
  22. expect(() => new Uploader(opts)).toThrow(new Uploader.ValidationError('upload destination does not match any allowed destinations'))
  23. })
  24. test('uploader respects uploadUrls, valid', async () => {
  25. const opts = {
  26. endpoint: 'http://url.myendpoint.com/files',
  27. companionOptions: { ...companionOptions, uploadUrls: [/^http:\/\/url.myendpoint.com\//] },
  28. }
  29. // eslint-disable-next-line no-new
  30. new Uploader(opts) // no validation error
  31. })
  32. test('uploader respects uploadUrls, localhost', async () => {
  33. const opts = {
  34. endpoint: 'http://localhost:1337/',
  35. companionOptions: { ...companionOptions, uploadUrls: [/^http:\/\/localhost:1337\//] },
  36. }
  37. // eslint-disable-next-line no-new
  38. new Uploader(opts) // no validation error
  39. })
  40. test('upload functions with tus protocol', async () => {
  41. const fileContent = Buffer.from('Some file content')
  42. const stream = intoStream(fileContent)
  43. const opts = {
  44. companionOptions,
  45. endpoint: 'http://url.myendpoint.com/files',
  46. protocol: 'tus',
  47. size: fileContent.length,
  48. pathPrefix: companionOptions.filePath,
  49. }
  50. const uploader = new Uploader(opts)
  51. const uploadToken = uploader.token
  52. expect(uploadToken).toBeTruthy()
  53. return new Promise((resolve, reject) => {
  54. // validate that the test is resolved on socket connection
  55. uploader.awaitReady().then(() => {
  56. uploader.tryUploadStream(stream).then(() => resolve())
  57. })
  58. let progressReceived = 0
  59. // emulate socket connection
  60. socketClient.connect(uploadToken)
  61. socketClient.onProgress(uploadToken, (message) => {
  62. progressReceived = message.payload.bytesUploaded
  63. try {
  64. expect(message.payload.bytesTotal).toBe(fileContent.length)
  65. } catch (err) {
  66. reject(err)
  67. }
  68. })
  69. socketClient.onUploadSuccess(uploadToken, (message) => {
  70. try {
  71. expect(progressReceived).toBe(fileContent.length)
  72. // see __mocks__/tus-js-client.js
  73. expect(message.payload.url).toBe('https://tus.endpoint/files/foo-bar')
  74. } catch (err) {
  75. reject(err)
  76. }
  77. })
  78. })
  79. })
  80. test('upload functions with tus protocol without size', async () => {
  81. const fileContent = Buffer.alloc(1e6)
  82. const stream = intoStream(fileContent)
  83. const opts = {
  84. companionOptions,
  85. endpoint: 'http://url.myendpoint.com/files',
  86. protocol: 'tus',
  87. size: null,
  88. pathPrefix: companionOptions.filePath,
  89. }
  90. const uploader = new Uploader(opts)
  91. const uploadToken = uploader.token
  92. expect(uploadToken).toBeTruthy()
  93. return new Promise((resolve, reject) => {
  94. // validate that the test is resolved on socket connection
  95. uploader.awaitReady().then(() => {
  96. uploader.tryUploadStream(stream).then(() => {
  97. try {
  98. expect(fs.existsSync(uploader.path)).toBe(false)
  99. resolve()
  100. } catch (err) {
  101. reject(err)
  102. }
  103. })
  104. })
  105. let progressReceived = 0
  106. // emulate socket connection
  107. socketClient.connect(uploadToken)
  108. socketClient.onProgress(uploadToken, (message) => {
  109. // validate that the file has been downloaded and saved into the file path
  110. try {
  111. progressReceived = message.payload.bytesUploaded
  112. if (progressReceived === fileContent.length) {
  113. const fileInfo = fs.statSync(uploader.tmpPath)
  114. expect(fileInfo.isFile()).toBe(true)
  115. expect(fileInfo.size).toBe(fileContent.length)
  116. expect(message.payload.bytesTotal).toBe(fileContent.length)
  117. }
  118. } catch (err) {
  119. reject(err)
  120. }
  121. })
  122. socketClient.onUploadSuccess(uploadToken, (message) => {
  123. try {
  124. expect(progressReceived).toBe(fileContent.length)
  125. // see __mocks__/tus-js-client.js
  126. expect(message.payload.url).toBe('https://tus.endpoint/files/foo-bar')
  127. } catch (err) {
  128. reject(err)
  129. }
  130. })
  131. })
  132. })
  133. async function runMultipartTest ({ metadata, useFormData, includeSize = true } = {}) {
  134. const fileContent = Buffer.from('Some file content')
  135. const stream = intoStream(fileContent)
  136. const opts = {
  137. companionOptions,
  138. endpoint: 'http://localhost',
  139. protocol: 'multipart',
  140. size: includeSize ? fileContent.length : undefined,
  141. metadata,
  142. pathPrefix: companionOptions.filePath,
  143. useFormData,
  144. }
  145. const uploader = new Uploader(opts)
  146. return uploader.uploadStream(stream)
  147. }
  148. test('upload functions with xhr protocol', async () => {
  149. nock('http://localhost').post('/').reply(200)
  150. const ret = await runMultipartTest()
  151. expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
  152. })
  153. // eslint-disable-next-line max-len
  154. const formDataNoMetaMatch = /^----------------------------\d+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n----------------------------\d+--\r\n$/
  155. test('upload functions with xhr formdata', async () => {
  156. nock('http://localhost').post('/', formDataNoMetaMatch)
  157. .reply(200)
  158. const ret = await runMultipartTest({ useFormData: true })
  159. expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
  160. })
  161. test('upload functions with unknown file size', async () => {
  162. // eslint-disable-next-line max-len
  163. nock('http://localhost').post('/', formDataNoMetaMatch)
  164. .reply(200)
  165. const ret = await runMultipartTest({ useFormData: true, includeSize: false })
  166. expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
  167. })
  168. // https://github.com/transloadit/uppy/issues/3477
  169. test('upload functions with xhr formdata and metadata', async () => {
  170. // eslint-disable-next-line max-len
  171. nock('http://localhost').post('/', /^----------------------------\d+\r\nContent-Disposition: form-data; name="key1"\r\n\r\nnull\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key2"\r\n\r\ntrue\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key3"\r\n\r\n\d+\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key4"\r\n\r\n\[object Object\]\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key5"\r\n\r\n\(\) => {}\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key6"\r\n\r\nSymbol\(\)\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n----------------------------\d+--\r\n$/)
  172. .reply(200)
  173. const metadata = {
  174. key1: null, key2: true, key3: 1234, key4: {}, key5: () => {}, key6: Symbol(''),
  175. }
  176. const ret = await runMultipartTest({ useFormData: true, metadata })
  177. expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
  178. })
  179. test('uploader checks metadata', () => {
  180. const opts = {
  181. companionOptions,
  182. endpoint: 'http://localhost',
  183. }
  184. // eslint-disable-next-line no-new
  185. new Uploader({ ...opts, metadata: { key: 'string value' } })
  186. expect(() => new Uploader({ ...opts, metadata: '' })).toThrow(new Uploader.ValidationError('metadata must be an object'))
  187. })
  188. test('uploader respects maxFileSize', async () => {
  189. const opts = {
  190. endpoint: 'http://url.myendpoint.com/files',
  191. companionOptions: { ...companionOptions, maxFileSize: 100 },
  192. size: 101,
  193. }
  194. expect(() => new Uploader(opts)).toThrow(new Uploader.ValidationError('maxFileSize exceeded'))
  195. })
  196. test('uploader respects maxFileSize correctly', async () => {
  197. const opts = {
  198. endpoint: 'http://url.myendpoint.com/files',
  199. companionOptions: { ...companionOptions, maxFileSize: 100 },
  200. size: 99,
  201. }
  202. // eslint-disable-next-line no-new
  203. new Uploader(opts) // no validation error
  204. })
  205. test('uploader respects maxFileSize with unknown size', async () => {
  206. const fileContent = Buffer.alloc(10000)
  207. const stream = intoStream(fileContent)
  208. const opts = {
  209. companionOptions: { ...companionOptions, maxFileSize: 1000 },
  210. endpoint: 'http://url.myendpoint.com/files',
  211. protocol: 'tus',
  212. size: null,
  213. pathPrefix: companionOptions.filePath,
  214. }
  215. const uploader = new Uploader(opts)
  216. const uploadToken = uploader.token
  217. // validate that the test is resolved on socket connection
  218. uploader.awaitReady().then(uploader.tryUploadStream(stream))
  219. socketClient.connect(uploadToken)
  220. return new Promise((resolve, reject) => {
  221. socketClient.onUploadError(uploadToken, (message) => {
  222. try {
  223. expect(message).toMatchObject({ payload: { error: { message: 'maxFileSize exceeded' } } })
  224. resolve()
  225. } catch (err) {
  226. reject(err)
  227. }
  228. })
  229. })
  230. })
  231. })