1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { pathExists, readdir, stat } from 'fs-extra'
6 import { join } from 'path'
7 import { HttpStatusCode } from '@shared/core-utils'
9 buildAbsoluteFixturePath,
13 prepareResumableUpload,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
19 } from '@shared/extra-utils'
20 import { MyUser, VideoPrivacy } from '@shared/models'
22 const expect = chai.expect
24 // Most classic resumable upload tests are done in other test suites
26 describe('Test resumable upload', function () {
27 const defaultFixture = 'video_short.mp4'
28 let server: ServerInfo
31 async function buildSize (fixture: string, size?: number) {
32 if (size !== undefined) return size
34 const baseFixture = buildAbsoluteFixturePath(fixture)
35 return (await stat(baseFixture)).size
38 async function prepareUpload (sizeArg?: number) {
39 const size = await buildSize(defaultFixture, sizeArg)
43 channelId: server.videoChannel.id,
44 privacy: VideoPrivacy.PUBLIC,
45 fixture: defaultFixture
48 const mimetype = 'video/mp4'
50 const res = await prepareResumableUpload({ url: server.url, token: server.accessToken, attributes, size, mimetype })
52 return res.header['location'].split('?')[1]
55 async function sendChunks (options: {
58 expectedStatus?: HttpStatusCode
59 contentLength?: number
61 contentRangeBuilder?: (start: number, chunk: any) => string
63 const { pathUploadId, expectedStatus, contentLength, contentRangeBuilder } = options
65 const size = await buildSize(defaultFixture, options.size)
66 const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
68 return sendResumableChunks({
70 token: server.accessToken,
72 videoFilePath: absoluteFilePath,
76 specialStatus: expectedStatus
80 async function checkFileSize (uploadIdArg: string, expectedSize: number | null) {
81 const uploadId = uploadIdArg.replace(/^upload_id=/, '')
83 const subPath = join('tmp', 'resumable-uploads', uploadId)
84 const filePath = server.serversCommand.buildDirectory(subPath)
85 const exists = await pathExists(filePath)
87 if (expectedSize === null) {
88 expect(exists).to.be.false
92 expect(exists).to.be.true
94 expect((await stat(filePath)).size).to.equal(expectedSize)
97 async function countResumableUploads () {
98 const subPath = join('tmp', 'resumable-uploads')
99 const filePath = server.serversCommand.buildDirectory(subPath)
101 const files = await readdir(filePath)
105 before(async function () {
108 server = await flushAndRunServer(1)
109 await setAccessTokensToServers([ server ])
110 await setDefaultVideoChannel([ server ])
112 const res = await getMyUserInformation(server.url, server.accessToken)
113 rootId = (res.body as MyUser).id
118 accessToken: server.accessToken,
119 videoQuota: 10_000_000
123 describe('Directory cleaning', function () {
125 it('Should correctly delete files after an upload', async function () {
126 const uploadId = await prepareUpload()
127 await sendChunks({ pathUploadId: uploadId })
129 expect(await countResumableUploads()).to.equal(0)
132 it('Should not delete files after an unfinished upload', async function () {
133 await prepareUpload()
135 expect(await countResumableUploads()).to.equal(2)
138 it('Should not delete recent uploads', async function () {
139 await server.debugCommand.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } })
141 expect(await countResumableUploads()).to.equal(2)
144 it('Should delete old uploads', async function () {
145 await server.debugCommand.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } })
147 expect(await countResumableUploads()).to.equal(0)
151 describe('Resumable upload and chunks', function () {
153 it('Should accept the same amount of chunks', async function () {
154 const uploadId = await prepareUpload()
155 await sendChunks({ pathUploadId: uploadId })
157 await checkFileSize(uploadId, null)
160 it('Should not accept more chunks than expected', async function () {
162 const uploadId = await prepareUpload(size)
164 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 })
165 await checkFileSize(uploadId, 0)
168 it('Should not accept more chunks than expected with an invalid content length/content range', async function () {
169 const uploadId = await prepareUpload(1500)
171 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 })
172 await checkFileSize(uploadId, 0)
175 it('Should not accept more chunks than expected with an invalid content length', async function () {
176 const uploadId = await prepareUpload(500)
180 const contentRangeBuilder = start => `bytes ${start}-${start + size - 1}/${size}`
181 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentRangeBuilder, contentLength: size })
182 await checkFileSize(uploadId, 0)
186 after(async function () {
187 await cleanupTests([ server ])