X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Ftests%2Fapi%2Fvideos%2Fresumable-upload.ts;h=b6d70ed44b2ef85d9b3936d914f705baf0782788;hb=0567049a9819d67070aa6d548a75a7e632a4aaa4;hp=2f1cf8a558691dba6f2f9ea32c55640870d7390b;hpb=89d241a79c262b9775c233b73cff080043ebb5e6;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/tests/api/videos/resumable-upload.ts b/server/tests/api/videos/resumable-upload.ts index 2f1cf8a55..b6d70ed44 100644 --- a/server/tests/api/videos/resumable-upload.ts +++ b/server/tests/api/videos/resumable-upload.ts @@ -4,16 +4,10 @@ import 'mocha' import * as chai from 'chai' import { pathExists, readdir, stat } from 'fs-extra' import { join } from 'path' -import { HttpStatusCode } from '@shared/core-utils' -import { - buildAbsoluteFixturePath, - cleanupTests, - flushAndRunServer, - ServerInfo, - setAccessTokensToServers, - setDefaultVideoChannel -} from '@shared/extra-utils' -import { VideoPrivacy } from '@shared/models' +import { buildAbsoluteFixturePath } from '@shared/core-utils' +import { sha1 } from '@shared/extra-utils' +import { HttpStatusCode, VideoPrivacy } from '@shared/models' +import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands' const expect = chai.expect @@ -21,8 +15,10 @@ const expect = chai.expect describe('Test resumable upload', function () { const defaultFixture = 'video_short.mp4' - let server: ServerInfo + let server: PeerTubeServer let rootId: number + let userAccessToken: string + let userChannelId: number async function buildSize (fixture: string, size?: number) { if (size !== undefined) return size @@ -31,42 +27,54 @@ describe('Test resumable upload', function () { return (await stat(baseFixture)).size } - async function prepareUpload (sizeArg?: number) { - const size = await buildSize(defaultFixture, sizeArg) + async function prepareUpload (options: { + channelId?: number + token?: string + size?: number + originalName?: string + lastModified?: number + } = {}) { + const { token, originalName, lastModified } = options + + const size = await buildSize(defaultFixture, options.size) const attributes = { name: 'video', - channelId: server.store.channel.id, + channelId: options.channelId ?? server.store.channel.id, privacy: VideoPrivacy.PUBLIC, fixture: defaultFixture } const mimetype = 'video/mp4' - const res = await server.videos.prepareResumableUpload({ attributes, size, mimetype }) + const res = await server.videos.prepareResumableUpload({ token, attributes, size, mimetype, originalName, lastModified }) return res.header['location'].split('?')[1] } async function sendChunks (options: { + token?: string pathUploadId: string size?: number expectedStatus?: HttpStatusCode contentLength?: number contentRange?: string contentRangeBuilder?: (start: number, chunk: any) => string + digestBuilder?: (chunk: any) => string }) { - const { pathUploadId, expectedStatus, contentLength, contentRangeBuilder } = options + const { token, pathUploadId, expectedStatus, contentLength, contentRangeBuilder, digestBuilder } = options const size = await buildSize(defaultFixture, options.size) const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture) return server.videos.sendResumableChunks({ + token, pathUploadId, videoFilePath: absoluteFilePath, size, contentLength, contentRangeBuilder, + digestBuilder, expectedStatus }) } @@ -99,24 +107,32 @@ describe('Test resumable upload', function () { before(async function () { this.timeout(30000) - server = await flushAndRunServer(1) + server = await createSingleServer(1) await setAccessTokensToServers([ server ]) await setDefaultVideoChannel([ server ]) const body = await server.users.getMyInfo() rootId = body.id + { + userAccessToken = await server.users.generateUserAndToken('user1') + const { videoChannels } = await server.users.getMyInfo({ token: userAccessToken }) + userChannelId = videoChannels[0].id + } + await server.users.update({ userId: rootId, videoQuota: 10_000_000 }) }) describe('Directory cleaning', function () { - it('Should correctly delete files after an upload', async function () { - const uploadId = await prepareUpload() - await sendChunks({ pathUploadId: uploadId }) + // FIXME: https://github.com/kukhariev/node-uploadx/pull/524/files#r852989382 + // it('Should correctly delete files after an upload', async function () { + // const uploadId = await prepareUpload() + // await sendChunks({ pathUploadId: uploadId }) + // await server.videos.endResumableUpload({ pathUploadId: uploadId }) - expect(await countResumableUploads()).to.equal(0) - }) + // expect(await countResumableUploads()).to.equal(0) + // }) it('Should not delete files after an unfinished upload', async function () { await prepareUpload() @@ -147,29 +163,122 @@ describe('Test resumable upload', function () { }) it('Should not accept more chunks than expected', async function () { - const size = 100 - const uploadId = await prepareUpload(size) + const uploadId = await prepareUpload({ size: 100 }) await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 }) await checkFileSize(uploadId, 0) }) it('Should not accept more chunks than expected with an invalid content length/content range', async function () { - const uploadId = await prepareUpload(1500) - - await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 }) - await checkFileSize(uploadId, 0) + const uploadId = await prepareUpload({ size: 1500 }) + + // Content length check seems to have changed in v16 + if (process.version.startsWith('v16')) { + await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409, contentLength: 1000 }) + await checkFileSize(uploadId, 1000) + } else { + await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 }) + await checkFileSize(uploadId, 0) + } }) it('Should not accept more chunks than expected with an invalid content length', async function () { - const uploadId = await prepareUpload(500) + const uploadId = await prepareUpload({ size: 500 }) const size = 1000 - const contentRangeBuilder = start => `bytes ${start}-${start + size - 1}/${size}` - await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentRangeBuilder, contentLength: size }) + // Content length check seems to have changed in v16 + const expectedStatus = process.version.startsWith('v16') + ? HttpStatusCode.CONFLICT_409 + : HttpStatusCode.BAD_REQUEST_400 + + const contentRangeBuilder = (start: number) => `bytes ${start}-${start + size - 1}/${size}` + await sendChunks({ pathUploadId: uploadId, expectedStatus, contentRangeBuilder, contentLength: size }) await checkFileSize(uploadId, 0) }) + + it('Should be able to accept 2 PUT requests', async function () { + const uploadId = await prepareUpload() + + const result1 = await sendChunks({ pathUploadId: uploadId }) + const result2 = await sendChunks({ pathUploadId: uploadId }) + + expect(result1.body.video.uuid).to.exist + expect(result1.body.video.uuid).to.equal(result2.body.video.uuid) + + expect(result1.headers['x-resumable-upload-cached']).to.not.exist + expect(result2.headers['x-resumable-upload-cached']).to.equal('true') + + await checkFileSize(uploadId, null) + }) + + it('Should not have the same upload id with 2 different users', async function () { + const originalName = 'toto.mp4' + const lastModified = new Date().getTime() + + const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken }) + const uploadId2 = await prepareUpload({ originalName, lastModified, channelId: userChannelId, token: userAccessToken }) + + expect(uploadId1).to.not.equal(uploadId2) + }) + + it('Should have the same upload id with the same user', async function () { + const originalName = 'toto.mp4' + const lastModified = new Date().getTime() + + const uploadId1 = await prepareUpload({ originalName, lastModified }) + const uploadId2 = await prepareUpload({ originalName, lastModified }) + + expect(uploadId1).to.equal(uploadId2) + }) + + it('Should not cache a request with 2 different users', async function () { + const originalName = 'toto.mp4' + const lastModified = new Date().getTime() + + const uploadId = await prepareUpload({ originalName, lastModified, token: server.accessToken }) + + await sendChunks({ pathUploadId: uploadId, token: server.accessToken }) + await sendChunks({ pathUploadId: uploadId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should not cache a request after a delete', async function () { + const originalName = 'toto.mp4' + const lastModified = new Date().getTime() + const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken }) + + await sendChunks({ pathUploadId: uploadId1 }) + await server.videos.endResumableUpload({ pathUploadId: uploadId1 }) + + const uploadId2 = await prepareUpload({ originalName, lastModified, token: server.accessToken }) + expect(uploadId1).to.equal(uploadId2) + + const result2 = await sendChunks({ pathUploadId: uploadId1 }) + expect(result2.headers['x-resumable-upload-cached']).to.not.exist + }) + + it('Should refuse an invalid digest', async function () { + const uploadId = await prepareUpload({ token: server.accessToken }) + + await sendChunks({ + pathUploadId: uploadId, + token: server.accessToken, + digestBuilder: () => 'sha=' + 'a'.repeat(40), + expectedStatus: 460 + }) + }) + + it('Should accept an appropriate digest', async function () { + const uploadId = await prepareUpload({ token: server.accessToken }) + + await sendChunks({ + pathUploadId: uploadId, + token: server.accessToken, + digestBuilder: (chunk: Buffer) => { + return 'sha1=' + sha1(chunk, 'base64') + } + }) + }) }) after(async function () {