project_blobs.test.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. const { expect } = require('chai')
  2. const config = require('config')
  3. const fs = require('node:fs')
  4. const fetch = require('node-fetch')
  5. const HTTPStatus = require('http-status')
  6. const cleanup = require('../storage/support/cleanup')
  7. const fixtures = require('../storage/support/fixtures')
  8. const testFiles = require('../storage/support/test_files')
  9. const testServer = require('./support/test_server')
  10. const { expectHttpError } = require('./support/expect_response')
  11. const { globalBlobs } = require('../../../../storage/lib/mongodb.js')
  12. const {
  13. loadGlobalBlobs,
  14. } = require('../../../../storage/lib/blob_store/index.js')
  15. describe('Project blobs API', function () {
  16. const projectId = '123'
  17. beforeEach(cleanup.everything)
  18. beforeEach(fixtures.create)
  19. let client
  20. let token
  21. before(async function () {
  22. client = await testServer.createClientForProject(projectId)
  23. token = testServer.createTokenForProject(projectId)
  24. })
  25. it('returns 404 if the blob is not found', async function () {
  26. const testHash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
  27. await expectHttpError(
  28. client.apis.Project.getProjectBlob({
  29. project_id: projectId,
  30. hash: testHash,
  31. }),
  32. HTTPStatus.NOT_FOUND
  33. )
  34. })
  35. it('checks if file hash matches the hash parameter', async function () {
  36. const testHash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
  37. const response = await fetch(
  38. testServer.url(`/api/projects/${projectId}/blobs/${testHash}`),
  39. {
  40. method: 'PUT',
  41. headers: { Authorization: `Bearer ${token}` },
  42. body: fs.createReadStream(testFiles.path('hello.txt')),
  43. }
  44. )
  45. expect(response.status).to.equal(HTTPStatus.CONFLICT)
  46. // check that it did not store the file
  47. await expectHttpError(
  48. client.apis.Project.getProjectBlob({
  49. project_id: projectId,
  50. hash: testFiles.HELLO_TXT_HASH,
  51. }),
  52. HTTPStatus.NOT_FOUND
  53. )
  54. })
  55. it('rejects oversized files', async function () {
  56. const testHash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
  57. const buffer = Buffer.alloc(
  58. parseInt(config.get('maxFileUploadSize'), 10) + 1
  59. )
  60. const response = await fetch(
  61. testServer.url(`/api/projects/${projectId}/blobs/${testHash}`),
  62. {
  63. method: 'PUT',
  64. headers: { Authorization: `Bearer ${token}` },
  65. body: buffer,
  66. }
  67. )
  68. expect(response.status).to.equal(HTTPStatus.REQUEST_ENTITY_TOO_LARGE)
  69. })
  70. describe('with an existing blob', async function () {
  71. let fileContents
  72. beforeEach(async function () {
  73. fileContents = await fs.promises.readFile(testFiles.path('hello.txt'))
  74. const response = await fetch(
  75. testServer.url(
  76. `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  77. ),
  78. {
  79. method: 'PUT',
  80. headers: { Authorization: `Bearer ${token}` },
  81. body: fileContents,
  82. }
  83. )
  84. expect(response.ok).to.be.true
  85. })
  86. it('fulfills a request with a JWT header', async function () {
  87. const response = await client.apis.Project.getProjectBlob({
  88. project_id: projectId,
  89. hash: testFiles.HELLO_TXT_HASH,
  90. })
  91. const responseText = await response.data.text()
  92. expect(responseText).to.equal(fileContents.toString())
  93. })
  94. it('fulfills a request with a token parameter', async function () {
  95. const url = new URL(
  96. testServer.url(
  97. `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  98. )
  99. )
  100. url.searchParams.append('token', token)
  101. const response = await fetch(url)
  102. const payload = await response.text()
  103. expect(payload).to.equal(fileContents.toString())
  104. })
  105. it('supports range request', async function () {
  106. const url = new URL(
  107. testServer.url(
  108. `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  109. )
  110. )
  111. url.searchParams.append('token', token)
  112. const response = await fetch(url, { headers: { Range: 'bytes=0-4' } })
  113. const payload = await response.text()
  114. expect(payload).to.equal(fileContents.toString().slice(0, 4))
  115. })
  116. it('supports HEAD request', async function () {
  117. const url = new URL(
  118. testServer.url(
  119. `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  120. )
  121. )
  122. url.searchParams.append('token', token)
  123. const response = await fetch(url, { method: 'HEAD' })
  124. expect(response.headers.get('Content-Length')).to.equal(
  125. testFiles.HELLO_TXT_BYTE_LENGTH.toString()
  126. )
  127. const payload = await response.text()
  128. expect(payload).to.have.length(0)
  129. })
  130. it('rejects an unautorized request', async function () {
  131. const response = await fetch(
  132. testServer.url(
  133. `/api/projects/${projectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  134. )
  135. )
  136. expect(response.status).to.equal(HTTPStatus.UNAUTHORIZED)
  137. })
  138. it('copies the blob to another project', async function () {
  139. const targetProjectId = '456'
  140. const targetClient =
  141. await testServer.createClientForProject(targetProjectId)
  142. const targetToken = testServer.createTokenForProject(targetProjectId)
  143. const url = new URL(
  144. testServer.url(
  145. `/api/projects/${targetProjectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  146. )
  147. )
  148. url.searchParams.append('copyFrom', projectId)
  149. const response = await fetch(url, {
  150. method: 'POST',
  151. headers: { Authorization: `Bearer ${targetToken}` },
  152. })
  153. expect(response.status).to.equal(HTTPStatus.CREATED)
  154. const newBlobResponse = await targetClient.apis.Project.getProjectBlob({
  155. project_id: targetProjectId,
  156. hash: testFiles.HELLO_TXT_HASH,
  157. })
  158. const newBlobResponseText = await newBlobResponse.data.text()
  159. expect(newBlobResponseText).to.equal(fileContents.toString())
  160. })
  161. it('skips copying a blob to another project if it already exists', async function () {
  162. const targetProjectId = '456'
  163. const targetClient =
  164. await testServer.createClientForProject(targetProjectId)
  165. const targetToken = testServer.createTokenForProject(targetProjectId)
  166. const fileContents = await fs.promises.readFile(
  167. testFiles.path('hello.txt')
  168. )
  169. const uploadResponse = await fetch(
  170. testServer.url(
  171. `/api/projects/${targetProjectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  172. ),
  173. {
  174. method: 'PUT',
  175. headers: { Authorization: `Bearer ${targetToken}` },
  176. body: fileContents,
  177. }
  178. )
  179. expect(uploadResponse.ok).to.be.true
  180. const url = new URL(
  181. testServer.url(
  182. `/api/projects/${targetProjectId}/blobs/${testFiles.HELLO_TXT_HASH}`
  183. )
  184. )
  185. url.searchParams.append('copyFrom', projectId)
  186. const response = await fetch(url, {
  187. method: 'POST',
  188. headers: { Authorization: `Bearer ${targetToken}` },
  189. })
  190. expect(response.status).to.equal(HTTPStatus.NO_CONTENT)
  191. const newBlobResponse = await targetClient.apis.Project.getProjectBlob({
  192. project_id: targetProjectId,
  193. hash: testFiles.HELLO_TXT_HASH,
  194. })
  195. const newBlobResponseText = await newBlobResponse.data.text()
  196. expect(newBlobResponseText).to.equal(fileContents.toString())
  197. })
  198. })
  199. describe('with a global blob', async function () {
  200. before(async function () {
  201. await globalBlobs.insertOne({
  202. _id: testFiles.STRING_A_HASH,
  203. byteLength: 1,
  204. stringLength: 1,
  205. })
  206. await loadGlobalBlobs()
  207. })
  208. it('does not copy global blobs', async function () {
  209. const targetProjectId = '456'
  210. const targetToken = testServer.createTokenForProject(targetProjectId)
  211. const url = new URL(
  212. testServer.url(
  213. `/api/projects/${targetProjectId}/blobs/${testFiles.STRING_A_HASH}`
  214. )
  215. )
  216. url.searchParams.append('copyFrom', projectId)
  217. const response = await fetch(url, {
  218. method: 'POST',
  219. headers: { Authorization: `Bearer ${targetToken}` },
  220. })
  221. expect(response.status).to.equal(HTTPStatus.NO_CONTENT)
  222. })
  223. })
  224. })