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,
14 prepareResumableUpload,
18 setAccessTokensToServers,
19 setDefaultVideoChannel,
21 } from '@shared/extra-utils'
22 import { MyUser, VideoPrivacy } from '@shared/models'
24 const expect = chai.expect
26 // Most classic resumable upload tests are done in other test suites
28 describe('Test resumable upload', function () {
29 const defaultFixture = 'video_short.mp4'
30 let server: ServerInfo
33 async function buildSize (fixture: string, size?: number) {
34 if (size !== undefined) return size
36 const baseFixture = buildAbsoluteFixturePath(fixture)
37 return (await stat(baseFixture)).size
40 async function prepareUpload (sizeArg?: number) {
41 const size = await buildSize(defaultFixture, sizeArg)
45 channelId: server.videoChannel.id,
46 privacy: VideoPrivacy.PUBLIC,
47 fixture: defaultFixture
50 const mimetype = 'video/mp4'
52 const res = await prepareResumableUpload({ url: server.url, token: server.accessToken, attributes, size, mimetype })
54 return res.header['location'].split('?')[1]
57 async function sendChunks (options: {
60 expectedStatus?: HttpStatusCode
61 contentLength?: number
63 contentRangeBuilder?: (start: number, chunk: any) => string
65 const { pathUploadId, expectedStatus, contentLength, contentRangeBuilder } = options
67 const size = await buildSize(defaultFixture, options.size)
68 const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
70 return sendResumableChunks({
72 token: server.accessToken,
74 videoFilePath: absoluteFilePath,
78 specialStatus: expectedStatus
82 async function checkFileSize (uploadIdArg: string, expectedSize: number | null) {
83 const uploadId = uploadIdArg.replace(/^upload_id=/, '')
85 const subPath = join('tmp', 'resumable-uploads', uploadId)
86 const filePath = buildServerDirectory(server, subPath)
87 const exists = await pathExists(filePath)
89 if (expectedSize === null) {
90 expect(exists).to.be.false
94 expect(exists).to.be.true
96 expect((await stat(filePath)).size).to.equal(expectedSize)
99 async function countResumableUploads () {
100 const subPath = join('tmp', 'resumable-uploads')
101 const filePath = buildServerDirectory(server, subPath)
103 const files = await readdir(filePath)
107 before(async function () {
110 server = await flushAndRunServer(1)
111 await setAccessTokensToServers([ server ])
112 await setDefaultVideoChannel([ server ])
114 const res = await getMyUserInformation(server.url, server.accessToken)
115 rootId = (res.body as MyUser).id
120 accessToken: server.accessToken,
121 videoQuota: 10_000_000
125 describe('Directory cleaning', function () {
127 it('Should correctly delete files after an upload', async function () {
128 const uploadId = await prepareUpload()
129 await sendChunks({ pathUploadId: uploadId })
131 expect(await countResumableUploads()).to.equal(0)
134 it('Should not delete files after an unfinished upload', async function () {
135 await prepareUpload()
137 expect(await countResumableUploads()).to.equal(2)
140 it('Should not delete recent uploads', async function () {
141 await sendDebugCommand(server.url, server.accessToken, { command: 'remove-dandling-resumable-uploads' })
143 expect(await countResumableUploads()).to.equal(2)
146 it('Should delete old uploads', async function () {
147 await sendDebugCommand(server.url, server.accessToken, { command: 'remove-dandling-resumable-uploads' })
149 expect(await countResumableUploads()).to.equal(0)
153 describe('Resumable upload and chunks', function () {
155 it('Should accept the same amount of chunks', async function () {
156 const uploadId = await prepareUpload()
157 await sendChunks({ pathUploadId: uploadId })
159 await checkFileSize(uploadId, null)
162 it('Should not accept more chunks than expected', async function () {
164 const uploadId = await prepareUpload(size)
166 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 })
167 await checkFileSize(uploadId, 0)
170 it('Should not accept more chunks than expected with an invalid content length/content range', async function () {
171 const uploadId = await prepareUpload(1500)
173 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 })
174 await checkFileSize(uploadId, 0)
177 it('Should not accept more chunks than expected with an invalid content length', async function () {
178 const uploadId = await prepareUpload(500)
182 const contentRangeBuilder = start => `bytes ${start}-${start + size - 1}/${size}`
183 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentRangeBuilder, contentLength: size })
184 await checkFileSize(uploadId, 0)
188 after(async function () {
189 await cleanupTests([ server ])