aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/videos
diff options
context:
space:
mode:
Diffstat (limited to 'server/tests/api/videos')
-rw-r--r--server/tests/api/videos/index.ts1
-rw-r--r--server/tests/api/videos/multiple-servers.ts25
-rw-r--r--server/tests/api/videos/resumable-upload.ts42
-rw-r--r--server/tests/api/videos/single-server.ts4
-rw-r--r--server/tests/api/videos/video-files.ts34
-rw-r--r--server/tests/api/videos/video-imports.ts14
-rw-r--r--server/tests/api/videos/video-passwords.ts97
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts18
-rw-r--r--server/tests/api/videos/video-playlists.ts33
-rw-r--r--server/tests/api/videos/video-static-file-privacy.ts129
-rw-r--r--server/tests/api/videos/video-storyboard.ts213
-rw-r--r--server/tests/api/videos/videos-common-filters.ts34
12 files changed, 553 insertions, 91 deletions
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts
index 357c08199..9c79b3aa6 100644
--- a/server/tests/api/videos/index.ts
+++ b/server/tests/api/videos/index.ts
@@ -20,3 +20,4 @@ import './videos-history'
20import './videos-overview' 20import './videos-overview'
21import './video-source' 21import './video-source'
22import './video-static-file-privacy' 22import './video-static-file-privacy'
23import './video-storyboard'
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 27ba00d3d..e9aa0e3a1 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -9,7 +9,7 @@ import {
9 completeVideoCheck, 9 completeVideoCheck,
10 dateIsValid, 10 dateIsValid,
11 saveVideoInServers, 11 saveVideoInServers,
12 testImage 12 testImageGeneratedByFFmpeg
13} from '@server/tests/shared' 13} from '@server/tests/shared'
14import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' 14import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
15import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models' 15import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
@@ -70,8 +70,9 @@ describe('Test multiple servers', function () {
70 }) 70 })
71 71
72 describe('Should upload the video and propagate on each server', function () { 72 describe('Should upload the video and propagate on each server', function () {
73
73 it('Should upload the video on server 1 and propagate on each server', async function () { 74 it('Should upload the video on server 1 and propagate on each server', async function () {
74 this.timeout(25000) 75 this.timeout(60000)
75 76
76 const attributes = { 77 const attributes = {
77 name: 'my super name for server 1', 78 name: 'my super name for server 1',
@@ -175,8 +176,8 @@ describe('Test multiple servers', function () {
175 support: 'my super support text for server 2', 176 support: 'my super support text for server 2',
176 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], 177 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
177 fixture: 'video_short2.webm', 178 fixture: 'video_short2.webm',
178 thumbnailfile: 'thumbnail.jpg', 179 thumbnailfile: 'custom-thumbnail.jpg',
179 previewfile: 'preview.jpg' 180 previewfile: 'custom-preview.jpg'
180 } 181 }
181 await servers[1].videos.upload({ token: userAccessToken, attributes, mode: 'resumable' }) 182 await servers[1].videos.upload({ token: userAccessToken, attributes, mode: 'resumable' })
182 183
@@ -229,8 +230,8 @@ describe('Test multiple servers', function () {
229 size: 750000 230 size: 750000
230 } 231 }
231 ], 232 ],
232 thumbnailfile: 'thumbnail', 233 thumbnailfile: 'custom-thumbnail',
233 previewfile: 'preview' 234 previewfile: 'custom-preview'
234 } 235 }
235 236
236 const { data } = await server.videos.list() 237 const { data } = await server.videos.list()
@@ -619,9 +620,9 @@ describe('Test multiple servers', function () {
619 description: 'my super description updated', 620 description: 'my super description updated',
620 support: 'my super support text updated', 621 support: 'my super support text updated',
621 tags: [ 'tag_up_1', 'tag_up_2' ], 622 tags: [ 'tag_up_1', 'tag_up_2' ],
622 thumbnailfile: 'thumbnail.jpg', 623 thumbnailfile: 'custom-thumbnail.jpg',
623 originallyPublishedAt: '2019-02-11T13:38:14.449Z', 624 originallyPublishedAt: '2019-02-11T13:38:14.449Z',
624 previewfile: 'preview.jpg' 625 previewfile: 'custom-preview.jpg'
625 } 626 }
626 627
627 updatedAtMin = new Date() 628 updatedAtMin = new Date()
@@ -674,8 +675,8 @@ describe('Test multiple servers', function () {
674 size: 292677 675 size: 292677
675 } 676 }
676 ], 677 ],
677 thumbnailfile: 'thumbnail', 678 thumbnailfile: 'custom-thumbnail',
678 previewfile: 'preview' 679 previewfile: 'custom-preview'
679 } 680 }
680 await completeVideoCheck({ server, originServer: servers[2], videoUUID: videoUpdated.uuid, attributes: checkAttributes }) 681 await completeVideoCheck({ server, originServer: servers[2], videoUUID: videoUpdated.uuid, attributes: checkAttributes })
681 } 682 }
@@ -685,7 +686,7 @@ describe('Test multiple servers', function () {
685 this.timeout(30000) 686 this.timeout(30000)
686 687
687 const attributes = { 688 const attributes = {
688 thumbnailfile: 'thumbnail.jpg' 689 thumbnailfile: 'custom-thumbnail.jpg'
689 } 690 }
690 691
691 updatedAtMin = new Date() 692 updatedAtMin = new Date()
@@ -761,7 +762,7 @@ describe('Test multiple servers', function () {
761 for (const server of servers) { 762 for (const server of servers) {
762 const video = await server.videos.get({ id: videoUUID }) 763 const video = await server.videos.get({ id: videoUUID })
763 764
764 await testImage(server.url, 'video_short1-preview.webm', video.previewPath) 765 await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath)
765 } 766 }
766 }) 767 })
767 }) 768 })
diff --git a/server/tests/api/videos/resumable-upload.ts b/server/tests/api/videos/resumable-upload.ts
index 2fbefb392..91eb61833 100644
--- a/server/tests/api/videos/resumable-upload.ts
+++ b/server/tests/api/videos/resumable-upload.ts
@@ -93,10 +93,10 @@ describe('Test resumable upload', function () {
93 expect((await stat(filePath)).size).to.equal(expectedSize) 93 expect((await stat(filePath)).size).to.equal(expectedSize)
94 } 94 }
95 95
96 async function countResumableUploads () { 96 async function countResumableUploads (wait?: number) {
97 const subPath = join('tmp', 'resumable-uploads') 97 const subPath = join('tmp', 'resumable-uploads')
98 const filePath = server.servers.buildDirectory(subPath) 98 const filePath = server.servers.buildDirectory(subPath)
99 99 await new Promise(resolve => setTimeout(resolve, wait))
100 const files = await readdir(filePath) 100 const files = await readdir(filePath)
101 return files.length 101 return files.length
102 } 102 }
@@ -122,14 +122,20 @@ describe('Test resumable upload', function () {
122 122
123 describe('Directory cleaning', function () { 123 describe('Directory cleaning', function () {
124 124
125 // FIXME: https://github.com/kukhariev/node-uploadx/pull/524/files#r852989382 125 it('Should correctly delete files after an upload', async function () {
126 // it('Should correctly delete files after an upload', async function () { 126 const uploadId = await prepareUpload()
127 // const uploadId = await prepareUpload() 127 await sendChunks({ pathUploadId: uploadId })
128 // await sendChunks({ pathUploadId: uploadId }) 128 await server.videos.endResumableUpload({ pathUploadId: uploadId })
129 // await server.videos.endResumableUpload({ pathUploadId: uploadId }) 129
130 expect(await countResumableUploads()).to.equal(0)
131 })
132
133 it('Should correctly delete corrupt files', async function () {
134 const uploadId = await prepareUpload({ size: 8 * 1024 })
135 await sendChunks({ pathUploadId: uploadId, size: 8 * 1024, expectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422 })
130 136
131 // expect(await countResumableUploads()).to.equal(0) 137 expect(await countResumableUploads(2000)).to.equal(0)
132 // }) 138 })
133 139
134 it('Should not delete files after an unfinished upload', async function () { 140 it('Should not delete files after an unfinished upload', async function () {
135 await prepareUpload() 141 await prepareUpload()
@@ -254,6 +260,24 @@ describe('Test resumable upload', function () {
254 expect(result2.headers['x-resumable-upload-cached']).to.not.exist 260 expect(result2.headers['x-resumable-upload-cached']).to.not.exist
255 }) 261 })
256 262
263 it('Should not cache after video deletion', async function () {
264 const originalName = 'toto.mp4'
265 const lastModified = new Date().getTime()
266
267 const uploadId1 = await prepareUpload({ originalName, lastModified })
268 const result1 = await sendChunks({ pathUploadId: uploadId1 })
269 await server.videos.remove({ id: result1.body.video.uuid })
270
271 const uploadId2 = await prepareUpload({ originalName, lastModified })
272 const result2 = await sendChunks({ pathUploadId: uploadId2 })
273 expect(result1.body.video.uuid).to.not.equal(result2.body.video.uuid)
274
275 expect(result2.headers['x-resumable-upload-cached']).to.not.exist
276
277 await checkFileSize(uploadId1, null)
278 await checkFileSize(uploadId2, null)
279 })
280
257 it('Should refuse an invalid digest', async function () { 281 it('Should refuse an invalid digest', async function () {
258 const uploadId = await prepareUpload({ token: server.accessToken }) 282 const uploadId = await prepareUpload({ token: server.accessToken })
259 283
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 0cb64d5a5..66414aa5b 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { checkVideoFilesWereRemoved, completeVideoCheck, testImage } from '@server/tests/shared' 4import { checkVideoFilesWereRemoved, completeVideoCheck, testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { wait } from '@shared/core-utils' 5import { wait } from '@shared/core-utils'
6import { Video, VideoPrivacy } from '@shared/models' 6import { Video, VideoPrivacy } from '@shared/models'
7import { 7import {
@@ -260,7 +260,7 @@ describe('Test a single server', function () {
260 260
261 for (const video of data) { 261 for (const video of data) {
262 const videoName = video.name.replace(' name', '') 262 const videoName = video.name.replace(' name', '')
263 await testImage(server.url, videoName, video.thumbnailPath) 263 await testImageGeneratedByFFmpeg(server.url, videoName, video.thumbnailPath)
264 } 264 }
265 }) 265 })
266 266
diff --git a/server/tests/api/videos/video-files.ts b/server/tests/api/videos/video-files.ts
index 8c913bf31..0a183c44d 100644
--- a/server/tests/api/videos/video-files.ts
+++ b/server/tests/api/videos/video-files.ts
@@ -48,10 +48,10 @@ describe('Test videos files', function () {
48 await waitJobs(servers) 48 await waitJobs(servers)
49 }) 49 })
50 50
51 it('Should delete webtorrent files', async function () { 51 it('Should delete web video files', async function () {
52 this.timeout(30_000) 52 this.timeout(30_000)
53 53
54 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId1 }) 54 await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1 })
55 55
56 await waitJobs(servers) 56 await waitJobs(servers)
57 57
@@ -80,15 +80,15 @@ describe('Test videos files', function () {
80 }) 80 })
81 81
82 describe('When deleting a specific file', function () { 82 describe('When deleting a specific file', function () {
83 let webtorrentId: string 83 let webVideoId: string
84 let hlsId: string 84 let hlsId: string
85 85
86 before(async function () { 86 before(async function () {
87 this.timeout(120_000) 87 this.timeout(120_000)
88 88
89 { 89 {
90 const { uuid } = await servers[0].videos.quickUpload({ name: 'webtorrent' }) 90 const { uuid } = await servers[0].videos.quickUpload({ name: 'web-video' })
91 webtorrentId = uuid 91 webVideoId = uuid
92 } 92 }
93 93
94 { 94 {
@@ -99,38 +99,38 @@ describe('Test videos files', function () {
99 await waitJobs(servers) 99 await waitJobs(servers)
100 }) 100 })
101 101
102 it('Shoulde delete a webtorrent file', async function () { 102 it('Shoulde delete a web video file', async function () {
103 this.timeout(30_000) 103 this.timeout(30_000)
104 104
105 const video = await servers[0].videos.get({ id: webtorrentId }) 105 const video = await servers[0].videos.get({ id: webVideoId })
106 const files = video.files 106 const files = video.files
107 107
108 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentId, fileId: files[0].id }) 108 await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: files[0].id })
109 109
110 await waitJobs(servers) 110 await waitJobs(servers)
111 111
112 for (const server of servers) { 112 for (const server of servers) {
113 const video = await server.videos.get({ id: webtorrentId }) 113 const video = await server.videos.get({ id: webVideoId })
114 114
115 expect(video.files).to.have.lengthOf(files.length - 1) 115 expect(video.files).to.have.lengthOf(files.length - 1)
116 expect(video.files.find(f => f.id === files[0].id)).to.not.exist 116 expect(video.files.find(f => f.id === files[0].id)).to.not.exist
117 } 117 }
118 }) 118 })
119 119
120 it('Should delete all webtorrent files', async function () { 120 it('Should delete all web video files', async function () {
121 this.timeout(30_000) 121 this.timeout(30_000)
122 122
123 const video = await servers[0].videos.get({ id: webtorrentId }) 123 const video = await servers[0].videos.get({ id: webVideoId })
124 const files = video.files 124 const files = video.files
125 125
126 for (const file of files) { 126 for (const file of files) {
127 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentId, fileId: file.id }) 127 await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: file.id })
128 } 128 }
129 129
130 await waitJobs(servers) 130 await waitJobs(servers)
131 131
132 for (const server of servers) { 132 for (const server of servers) {
133 const video = await server.videos.get({ id: webtorrentId }) 133 const video = await server.videos.get({ id: webVideoId })
134 134
135 expect(video.files).to.have.lengthOf(0) 135 expect(video.files).to.have.lengthOf(0)
136 } 136 }
@@ -182,16 +182,16 @@ describe('Test videos files', function () {
182 it('Should not delete last file of a video', async function () { 182 it('Should not delete last file of a video', async function () {
183 this.timeout(60_000) 183 this.timeout(60_000)
184 184
185 const webtorrentOnly = await servers[0].videos.get({ id: hlsId }) 185 const webVideoOnly = await servers[0].videos.get({ id: hlsId })
186 const hlsOnly = await servers[0].videos.get({ id: webtorrentId }) 186 const hlsOnly = await servers[0].videos.get({ id: webVideoId })
187 187
188 for (let i = 0; i < 4; i++) { 188 for (let i = 0; i < 4; i++) {
189 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentOnly.id, fileId: webtorrentOnly.files[i].id }) 189 await servers[0].videos.removeWebVideoFile({ videoId: webVideoOnly.id, fileId: webVideoOnly.files[i].id })
190 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[i].id }) 190 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[i].id })
191 } 191 }
192 192
193 const expectedStatus = HttpStatusCode.BAD_REQUEST_400 193 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
194 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentOnly.id, fileId: webtorrentOnly.files[4].id, expectedStatus }) 194 await servers[0].videos.removeWebVideoFile({ videoId: webVideoOnly.id, fileId: webVideoOnly.files[4].id, expectedStatus })
195 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[4].id, expectedStatus }) 195 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[4].id, expectedStatus })
196 }) 196 })
197 }) 197 })
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index 192b2aeb9..b78b4f344 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -3,7 +3,7 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { pathExists, readdir, remove } from 'fs-extra' 4import { pathExists, readdir, remove } from 'fs-extra'
5import { join } from 'path' 5import { join } from 'path'
6import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared' 6import { FIXTURE_URLS, testCaptionFile, testImageGeneratedByFFmpeg } from '@server/tests/shared'
7import { areHttpImportTestsDisabled } from '@shared/core-utils' 7import { areHttpImportTestsDisabled } from '@shared/core-utils'
8import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models' 8import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models'
9import { 9import {
@@ -67,7 +67,7 @@ async function checkVideoServer2 (server: PeerTubeServer, id: number | string) {
67 expect(video.description).to.equal('my super description') 67 expect(video.description).to.equal('my super description')
68 expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ]) 68 expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ])
69 69
70 await testImage(server.url, 'thumbnail', video.thumbnailPath) 70 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', video.thumbnailPath)
71 71
72 expect(video.files).to.have.lengthOf(1) 72 expect(video.files).to.have.lengthOf(1)
73 73
@@ -119,15 +119,15 @@ describe('Test video imports', function () {
119 expect(video.name).to.equal('small video - youtube') 119 expect(video.name).to.equal('small video - youtube')
120 120
121 { 121 {
122 expect(video.thumbnailPath).to.match(new RegExp(`^/static/thumbnails/.+.jpg$`)) 122 expect(video.thumbnailPath).to.match(new RegExp(`^/lazy-static/thumbnails/.+.jpg$`))
123 expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`)) 123 expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`))
124 124
125 const suffix = mode === 'yt-dlp' 125 const suffix = mode === 'yt-dlp'
126 ? '_yt_dlp' 126 ? '_yt_dlp'
127 : '' 127 : ''
128 128
129 await testImage(servers[0].url, 'video_import_thumbnail' + suffix, video.thumbnailPath) 129 await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_thumbnail' + suffix, video.thumbnailPath)
130 await testImage(servers[0].url, 'video_import_preview' + suffix, video.previewPath) 130 await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_preview' + suffix, video.previewPath)
131 } 131 }
132 132
133 const bodyCaptions = await servers[0].captions.list({ videoId: video.id }) 133 const bodyCaptions = await servers[0].captions.list({ videoId: video.id })
@@ -266,7 +266,7 @@ describe('Test video imports', function () {
266 name: 'my super name', 266 name: 'my super name',
267 description: 'my super description', 267 description: 'my super description',
268 tags: [ 'supertag1', 'supertag2' ], 268 tags: [ 'supertag1', 'supertag2' ],
269 thumbnailfile: 'thumbnail.jpg' 269 thumbnailfile: 'custom-thumbnail.jpg'
270 } 270 }
271 }) 271 })
272 expect(video.name).to.equal('my super name') 272 expect(video.name).to.equal('my super name')
@@ -328,7 +328,7 @@ describe('Test video imports', function () {
328 '1440p': false, 328 '1440p': false,
329 '2160p': false 329 '2160p': false
330 }, 330 },
331 webtorrent: { enabled: true }, 331 webVideos: { enabled: true },
332 hls: { enabled: false } 332 hls: { enabled: false }
333 } 333 }
334 } 334 }
diff --git a/server/tests/api/videos/video-passwords.ts b/server/tests/api/videos/video-passwords.ts
new file mode 100644
index 000000000..e01a93a4d
--- /dev/null
+++ b/server/tests/api/videos/video-passwords.ts
@@ -0,0 +1,97 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import {
5 cleanupTests,
6 createSingleServer,
7 VideoPasswordsCommand,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 setDefaultAccountAvatar,
11 setDefaultChannelAvatar
12} from '@shared/server-commands'
13import { VideoPrivacy } from '@shared/models'
14
15describe('Test video passwords', function () {
16 let server: PeerTubeServer
17 let videoUUID: string
18
19 let userAccessTokenServer1: string
20
21 let videoPasswords: string[] = []
22 let command: VideoPasswordsCommand
23
24 before(async function () {
25 this.timeout(30000)
26
27 server = await createSingleServer(1)
28
29 await setAccessTokensToServers([ server ])
30
31 for (let i = 0; i < 10; i++) {
32 videoPasswords.push(`password ${i + 1}`)
33 }
34 const { uuid } = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PASSWORD_PROTECTED, videoPasswords } })
35 videoUUID = uuid
36
37 await setDefaultChannelAvatar(server)
38 await setDefaultAccountAvatar(server)
39
40 userAccessTokenServer1 = await server.users.generateUserAndToken('user1')
41 await setDefaultChannelAvatar(server, 'user1_channel')
42 await setDefaultAccountAvatar(server, userAccessTokenServer1)
43
44 command = server.videoPasswords
45 })
46
47 it('Should list video passwords', async function () {
48 const body = await command.list({ videoId: videoUUID })
49
50 expect(body.total).to.equal(10)
51 expect(body.data).to.be.an('array')
52 expect(body.data).to.have.lengthOf(10)
53 })
54
55 it('Should filter passwords on this video', async function () {
56 const body = await command.list({ videoId: videoUUID, count: 2, start: 3, sort: 'createdAt' })
57
58 expect(body.total).to.equal(10)
59 expect(body.data).to.be.an('array')
60 expect(body.data).to.have.lengthOf(2)
61 expect(body.data[0].password).to.equal('password 4')
62 expect(body.data[1].password).to.equal('password 5')
63 })
64
65 it('Should update password for this video', async function () {
66 videoPasswords = [ 'my super new password 1', 'my super new password 2' ]
67
68 await command.updateAll({ videoId: videoUUID, passwords: videoPasswords })
69 const body = await command.list({ videoId: videoUUID })
70 expect(body.total).to.equal(2)
71 expect(body.data).to.be.an('array')
72 expect(body.data).to.have.lengthOf(2)
73 expect(body.data[0].password).to.equal('my super new password 2')
74 expect(body.data[1].password).to.equal('my super new password 1')
75 })
76
77 it('Should delete one password', async function () {
78 {
79 const body = await command.list({ videoId: videoUUID })
80 expect(body.total).to.equal(2)
81 expect(body.data).to.be.an('array')
82 expect(body.data).to.have.lengthOf(2)
83 await command.remove({ id: body.data[0].id, videoId: videoUUID })
84 }
85 {
86 const body = await command.list({ videoId: videoUUID })
87
88 expect(body.total).to.equal(1)
89 expect(body.data).to.be.an('array')
90 expect(body.data).to.have.lengthOf(1)
91 }
92 })
93
94 after(async function () {
95 await cleanupTests([ server ])
96 })
97})
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
index 356939b93..c274c20bf 100644
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ b/server/tests/api/videos/video-playlist-thumbnails.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { testImage } from '@server/tests/shared' 4import { testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { VideoPlaylistPrivacy } from '@shared/models' 5import { VideoPlaylistPrivacy } from '@shared/models'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
@@ -83,7 +83,7 @@ describe('Playlist thumbnail', function () {
83 83
84 for (const server of servers) { 84 for (const server of servers) {
85 const p = await getPlaylistWithoutThumbnail(server) 85 const p = await getPlaylistWithoutThumbnail(server)
86 await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) 86 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
87 } 87 }
88 }) 88 })
89 89
@@ -95,7 +95,7 @@ describe('Playlist thumbnail', function () {
95 displayName: 'playlist with thumbnail', 95 displayName: 'playlist with thumbnail',
96 privacy: VideoPlaylistPrivacy.PUBLIC, 96 privacy: VideoPlaylistPrivacy.PUBLIC,
97 videoChannelId: servers[1].store.channel.id, 97 videoChannelId: servers[1].store.channel.id,
98 thumbnailfile: 'thumbnail.jpg' 98 thumbnailfile: 'custom-thumbnail.jpg'
99 } 99 }
100 }) 100 })
101 playlistWithThumbnailId = created.id 101 playlistWithThumbnailId = created.id
@@ -110,7 +110,7 @@ describe('Playlist thumbnail', function () {
110 110
111 for (const server of servers) { 111 for (const server of servers) {
112 const p = await getPlaylistWithThumbnail(server) 112 const p = await getPlaylistWithThumbnail(server)
113 await testImage(server.url, 'thumbnail', p.thumbnailPath) 113 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
114 } 114 }
115 }) 115 })
116 116
@@ -135,7 +135,7 @@ describe('Playlist thumbnail', function () {
135 135
136 for (const server of servers) { 136 for (const server of servers) {
137 const p = await getPlaylistWithoutThumbnail(server) 137 const p = await getPlaylistWithoutThumbnail(server)
138 await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) 138 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
139 } 139 }
140 }) 140 })
141 141
@@ -160,7 +160,7 @@ describe('Playlist thumbnail', function () {
160 160
161 for (const server of servers) { 161 for (const server of servers) {
162 const p = await getPlaylistWithThumbnail(server) 162 const p = await getPlaylistWithThumbnail(server)
163 await testImage(server.url, 'thumbnail', p.thumbnailPath) 163 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
164 } 164 }
165 }) 165 })
166 166
@@ -176,7 +176,7 @@ describe('Playlist thumbnail', function () {
176 176
177 for (const server of servers) { 177 for (const server of servers) {
178 const p = await getPlaylistWithoutThumbnail(server) 178 const p = await getPlaylistWithoutThumbnail(server)
179 await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) 179 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
180 } 180 }
181 }) 181 })
182 182
@@ -192,7 +192,7 @@ describe('Playlist thumbnail', function () {
192 192
193 for (const server of servers) { 193 for (const server of servers) {
194 const p = await getPlaylistWithThumbnail(server) 194 const p = await getPlaylistWithThumbnail(server)
195 await testImage(server.url, 'thumbnail', p.thumbnailPath) 195 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
196 } 196 }
197 }) 197 })
198 198
@@ -224,7 +224,7 @@ describe('Playlist thumbnail', function () {
224 224
225 for (const server of servers) { 225 for (const server of servers) {
226 const p = await getPlaylistWithThumbnail(server) 226 const p = await getPlaylistWithThumbnail(server)
227 await testImage(server.url, 'thumbnail', p.thumbnailPath) 227 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
228 } 228 }
229 }) 229 })
230 230
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index d9c5bdf16..3bfa874cb 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared' 4import { checkPlaylistFilesWereRemoved, testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { wait } from '@shared/core-utils' 5import { wait } from '@shared/core-utils'
6import { uuidToShort } from '@shared/extra-utils' 6import { uuidToShort } from '@shared/extra-utils'
7import { 7import {
@@ -133,7 +133,7 @@ describe('Test video playlists', function () {
133 displayName: 'my super playlist', 133 displayName: 'my super playlist',
134 privacy: VideoPlaylistPrivacy.PUBLIC, 134 privacy: VideoPlaylistPrivacy.PUBLIC,
135 description: 'my super description', 135 description: 'my super description',
136 thumbnailfile: 'thumbnail.jpg', 136 thumbnailfile: 'custom-thumbnail.jpg',
137 videoChannelId: servers[0].store.channel.id 137 videoChannelId: servers[0].store.channel.id
138 } 138 }
139 }) 139 })
@@ -225,7 +225,7 @@ describe('Test video playlists', function () {
225 displayName: 'my super playlist', 225 displayName: 'my super playlist',
226 privacy: VideoPlaylistPrivacy.PUBLIC, 226 privacy: VideoPlaylistPrivacy.PUBLIC,
227 description: 'my super description', 227 description: 'my super description',
228 thumbnailfile: 'thumbnail.jpg', 228 thumbnailfile: 'custom-thumbnail.jpg',
229 videoChannelId: servers[0].store.channel.id 229 videoChannelId: servers[0].store.channel.id
230 } 230 }
231 }) 231 })
@@ -286,7 +286,7 @@ describe('Test video playlists', function () {
286 attributes: { 286 attributes: {
287 displayName: 'playlist 3', 287 displayName: 'playlist 3',
288 privacy: VideoPlaylistPrivacy.PUBLIC, 288 privacy: VideoPlaylistPrivacy.PUBLIC,
289 thumbnailfile: 'thumbnail.jpg', 289 thumbnailfile: 'custom-thumbnail.jpg',
290 videoChannelId: servers[1].store.channel.id 290 videoChannelId: servers[1].store.channel.id
291 } 291 }
292 }) 292 })
@@ -314,11 +314,11 @@ describe('Test video playlists', function () {
314 314
315 const playlist2 = body.data.find(p => p.displayName === 'playlist 2') 315 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
316 expect(playlist2).to.not.be.undefined 316 expect(playlist2).to.not.be.undefined
317 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) 317 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
318 318
319 const playlist3 = body.data.find(p => p.displayName === 'playlist 3') 319 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
320 expect(playlist3).to.not.be.undefined 320 expect(playlist3).to.not.be.undefined
321 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) 321 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', playlist3.thumbnailPath)
322 } 322 }
323 323
324 const body = await servers[2].playlists.list({ start: 0, count: 5 }) 324 const body = await servers[2].playlists.list({ start: 0, count: 5 })
@@ -336,7 +336,7 @@ describe('Test video playlists', function () {
336 336
337 const playlist2 = body.data.find(p => p.displayName === 'playlist 2') 337 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
338 expect(playlist2).to.not.be.undefined 338 expect(playlist2).to.not.be.undefined
339 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) 339 await testImageGeneratedByFFmpeg(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
340 340
341 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined 341 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
342 }) 342 })
@@ -474,7 +474,7 @@ describe('Test video playlists', function () {
474 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 }) 474 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
475 }) 475 })
476 476
477 it('Should get unlisted plyaylist using uuid or shortUUID', async function () { 477 it('Should get unlisted playlist using uuid or shortUUID', async function () {
478 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid }) 478 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
479 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID }) 479 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
480 }) 480 })
@@ -502,7 +502,7 @@ describe('Test video playlists', function () {
502 displayName: 'playlist 3 updated', 502 displayName: 'playlist 3 updated',
503 description: 'description updated', 503 description: 'description updated',
504 privacy: VideoPlaylistPrivacy.UNLISTED, 504 privacy: VideoPlaylistPrivacy.UNLISTED,
505 thumbnailfile: 'thumbnail.jpg', 505 thumbnailfile: 'custom-thumbnail.jpg',
506 videoChannelId: servers[1].store.channel.id 506 videoChannelId: servers[1].store.channel.id
507 }, 507 },
508 playlistId: playlistServer2Id2 508 playlistId: playlistServer2Id2
@@ -686,7 +686,7 @@ describe('Test video playlists', function () {
686 await waitJobs(servers) 686 await waitJobs(servers)
687 }) 687 })
688 688
689 it('Should update the element type if the video is private', async function () { 689 it('Should update the element type if the video is private/password protected', async function () {
690 this.timeout(20000) 690 this.timeout(20000)
691 691
692 const name = 'video 89' 692 const name = 'video 89'
@@ -703,6 +703,19 @@ describe('Test video playlists', function () {
703 } 703 }
704 704
705 { 705 {
706 await servers[0].videos.update({
707 id: video1,
708 attributes: { privacy: VideoPrivacy.PASSWORD_PROTECTED, videoPasswords: [ 'password' ] }
709 })
710 await waitJobs(servers)
711
712 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
713 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
714 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
715 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
716 }
717
718 {
706 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } }) 719 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
707 await waitJobs(servers) 720 await waitJobs(servers)
708 721
diff --git a/server/tests/api/videos/video-static-file-privacy.ts b/server/tests/api/videos/video-static-file-privacy.ts
index 542848533..0a9864134 100644
--- a/server/tests/api/videos/video-static-file-privacy.ts
+++ b/server/tests/api/videos/video-static-file-privacy.ts
@@ -41,7 +41,7 @@ describe('Test video static file privacy', function () {
41 41
42 for (const file of video.files) { 42 for (const file of video.files) {
43 expect(file.fileDownloadUrl).to.not.include('/private/') 43 expect(file.fileDownloadUrl).to.not.include('/private/')
44 expectStartWith(file.fileUrl, server.url + '/static/webseed/private/') 44 expectStartWith(file.fileUrl, server.url + '/static/web-videos/private/')
45 45
46 const torrent = await parseTorrentVideo(server, file) 46 const torrent = await parseTorrentVideo(server, file)
47 expect(torrent.urlList).to.have.lengthOf(0) 47 expect(torrent.urlList).to.have.lengthOf(0)
@@ -90,7 +90,7 @@ describe('Test video static file privacy', function () {
90 } 90 }
91 } 91 }
92 92
93 it('Should upload a private/internal video and have a private static path', async function () { 93 it('Should upload a private/internal/password protected video and have a private static path', async function () {
94 this.timeout(120000) 94 this.timeout(120000)
95 95
96 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) { 96 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
@@ -99,6 +99,15 @@ describe('Test video static file privacy', function () {
99 99
100 await checkPrivateFiles(uuid) 100 await checkPrivateFiles(uuid)
101 } 101 }
102
103 const { uuid } = await server.videos.quickUpload({
104 name: 'video',
105 privacy: VideoPrivacy.PASSWORD_PROTECTED,
106 videoPasswords: [ 'my super password' ]
107 })
108 await waitJobs([ server ])
109
110 await checkPrivateFiles(uuid)
102 }) 111 })
103 112
104 it('Should upload a public video and update it as private/internal to have a private static path', async function () { 113 it('Should upload a public video and update it as private/internal to have a private static path', async function () {
@@ -185,8 +194,9 @@ describe('Test video static file privacy', function () {
185 expectedStatus: HttpStatusCode 194 expectedStatus: HttpStatusCode
186 token: string 195 token: string
187 videoFileToken: string 196 videoFileToken: string
197 videoPassword?: string
188 }) { 198 }) {
189 const { id, expectedStatus, token, videoFileToken } = options 199 const { id, expectedStatus, token, videoFileToken, videoPassword } = options
190 200
191 const video = await server.videos.getWithToken({ id }) 201 const video = await server.videos.getWithToken({ id })
192 202
@@ -196,6 +206,12 @@ describe('Test video static file privacy', function () {
196 206
197 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken }, expectedStatus }) 207 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken }, expectedStatus })
198 await makeRawRequest({ url: file.fileDownloadUrl, query: { videoFileToken }, expectedStatus }) 208 await makeRawRequest({ url: file.fileDownloadUrl, query: { videoFileToken }, expectedStatus })
209
210 if (videoPassword) {
211 const headers = { 'x-peertube-video-password': videoPassword }
212 await makeRawRequest({ url: file.fileUrl, headers, expectedStatus })
213 await makeRawRequest({ url: file.fileDownloadUrl, headers, expectedStatus })
214 }
199 } 215 }
200 216
201 const hls = video.streamingPlaylists[0] 217 const hls = video.streamingPlaylists[0]
@@ -204,6 +220,12 @@ describe('Test video static file privacy', function () {
204 220
205 await makeRawRequest({ url: hls.playlistUrl, query: { videoFileToken }, expectedStatus }) 221 await makeRawRequest({ url: hls.playlistUrl, query: { videoFileToken }, expectedStatus })
206 await makeRawRequest({ url: hls.segmentsSha256Url, query: { videoFileToken }, expectedStatus }) 222 await makeRawRequest({ url: hls.segmentsSha256Url, query: { videoFileToken }, expectedStatus })
223
224 if (videoPassword) {
225 const headers = { 'x-peertube-video-password': videoPassword }
226 await makeRawRequest({ url: hls.playlistUrl, token: null, headers, expectedStatus })
227 await makeRawRequest({ url: hls.segmentsSha256Url, token: null, headers, expectedStatus })
228 }
207 } 229 }
208 230
209 before(async function () { 231 before(async function () {
@@ -216,13 +238,53 @@ describe('Test video static file privacy', function () {
216 it('Should not be able to access a private video files without OAuth token and file token', async function () { 238 it('Should not be able to access a private video files without OAuth token and file token', async function () {
217 this.timeout(120000) 239 this.timeout(120000)
218 240
219 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL }) 241 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
220 await waitJobs([ server ]) 242 await waitJobs([ server ])
221 243
222 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: null, videoFileToken: null }) 244 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: null, videoFileToken: null })
223 }) 245 })
224 246
225 it('Should not be able to access an internal video files without appropriate OAuth token and file token', async function () { 247 it('Should not be able to access password protected video files without OAuth token, file token and password', async function () {
248 this.timeout(120000)
249 const videoPassword = 'my super password'
250
251 const { uuid } = await server.videos.quickUpload({
252 name: 'password protected video',
253 privacy: VideoPrivacy.PASSWORD_PROTECTED,
254 videoPasswords: [ videoPassword ]
255 })
256 await waitJobs([ server ])
257
258 await checkVideoFiles({
259 id: uuid,
260 expectedStatus: HttpStatusCode.FORBIDDEN_403,
261 token: null,
262 videoFileToken: null,
263 videoPassword: null
264 })
265 })
266
267 it('Should not be able to access an password video files with incorrect OAuth token, file token and password', async function () {
268 this.timeout(120000)
269 const videoPassword = 'my super password'
270
271 const { uuid } = await server.videos.quickUpload({
272 name: 'password protected video',
273 privacy: VideoPrivacy.PASSWORD_PROTECTED,
274 videoPasswords: [ videoPassword ]
275 })
276 await waitJobs([ server ])
277
278 await checkVideoFiles({
279 id: uuid,
280 expectedStatus: HttpStatusCode.FORBIDDEN_403,
281 token: userToken,
282 videoFileToken: unrelatedFileToken,
283 videoPassword: 'incorrectPassword'
284 })
285 })
286
287 it('Should not be able to access an private video files without appropriate OAuth token and file token', async function () {
226 this.timeout(120000) 288 this.timeout(120000)
227 289
228 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) 290 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
@@ -247,6 +309,23 @@ describe('Test video static file privacy', function () {
247 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken }) 309 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
248 }) 310 })
249 311
312 it('Should be able to access a password protected video files with appropriate OAuth token or file token', async function () {
313 this.timeout(120000)
314 const videoPassword = 'my super password'
315
316 const { uuid } = await server.videos.quickUpload({
317 name: 'video',
318 privacy: VideoPrivacy.PASSWORD_PROTECTED,
319 videoPasswords: [ videoPassword ]
320 })
321
322 const videoFileToken = await server.videoToken.getVideoFileToken({ token: null, videoId: uuid, videoPassword })
323
324 await waitJobs([ server ])
325
326 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken, videoPassword })
327 })
328
250 it('Should reinject video file token', async function () { 329 it('Should reinject video file token', async function () {
251 this.timeout(120000) 330 this.timeout(120000)
252 331
@@ -294,13 +373,20 @@ describe('Test video static file privacy', function () {
294 let permanentLiveId: string 373 let permanentLiveId: string
295 let permanentLive: LiveVideo 374 let permanentLive: LiveVideo
296 375
376 let passwordProtectedLiveId: string
377 let passwordProtectedLive: LiveVideo
378
379 const correctPassword = 'my super password'
380
297 let unrelatedFileToken: string 381 let unrelatedFileToken: string
298 382
299 async function checkLiveFiles (live: LiveVideo, liveId: string) { 383 async function checkLiveFiles (options: { live: LiveVideo, liveId: string, videoPassword?: string }) {
384 const { live, liveId, videoPassword } = options
300 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey }) 385 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
301 await server.live.waitUntilPublished({ videoId: liveId }) 386 await server.live.waitUntilPublished({ videoId: liveId })
302 387
303 const video = await server.videos.getWithToken({ id: liveId }) 388 const video = await server.videos.getWithToken({ id: liveId })
389
304 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid }) 390 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
305 391
306 const hls = video.streamingPlaylists[0] 392 const hls = video.streamingPlaylists[0]
@@ -314,6 +400,16 @@ describe('Test video static file privacy', function () {
314 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) 400 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
315 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) 401 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
316 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) 402 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
403
404 if (videoPassword) {
405 await makeRawRequest({ url, headers: { 'x-peertube-video-password': videoPassword }, expectedStatus: HttpStatusCode.OK_200 })
406 await makeRawRequest({
407 url,
408 headers: { 'x-peertube-video-password': 'incorrectPassword' },
409 expectedStatus: HttpStatusCode.FORBIDDEN_403
410 })
411 }
412
317 } 413 }
318 414
319 await stopFfmpeg(ffmpegCommand) 415 await stopFfmpeg(ffmpegCommand)
@@ -381,18 +477,35 @@ describe('Test video static file privacy', function () {
381 permanentLiveId = video.uuid 477 permanentLiveId = video.uuid
382 permanentLive = live 478 permanentLive = live
383 } 479 }
480
481 {
482 const { video, live } = await server.live.quickCreate({
483 saveReplay: false,
484 permanentLive: false,
485 privacy: VideoPrivacy.PASSWORD_PROTECTED,
486 videoPasswords: [ correctPassword ]
487 })
488 passwordProtectedLiveId = video.uuid
489 passwordProtectedLive = live
490 }
384 }) 491 })
385 492
386 it('Should create a private normal live and have a private static path', async function () { 493 it('Should create a private normal live and have a private static path', async function () {
387 this.timeout(240000) 494 this.timeout(240000)
388 495
389 await checkLiveFiles(normalLive, normalLiveId) 496 await checkLiveFiles({ live: normalLive, liveId: normalLiveId })
390 }) 497 })
391 498
392 it('Should create a private permanent live and have a private static path', async function () { 499 it('Should create a private permanent live and have a private static path', async function () {
393 this.timeout(240000) 500 this.timeout(240000)
394 501
395 await checkLiveFiles(permanentLive, permanentLiveId) 502 await checkLiveFiles({ live: permanentLive, liveId: permanentLiveId })
503 })
504
505 it('Should create a password protected live and have a private static path', async function () {
506 this.timeout(240000)
507
508 await checkLiveFiles({ live: passwordProtectedLive, liveId: passwordProtectedLiveId, videoPassword: correctPassword })
396 }) 509 })
397 510
398 it('Should reinject video file token on permanent live', async function () { 511 it('Should reinject video file token on permanent live', async function () {
diff --git a/server/tests/api/videos/video-storyboard.ts b/server/tests/api/videos/video-storyboard.ts
new file mode 100644
index 000000000..fc4b4450f
--- /dev/null
+++ b/server/tests/api/videos/video-storyboard.ts
@@ -0,0 +1,213 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { readdir } from 'fs-extra'
5import { basename } from 'path'
6import { FIXTURE_URLS } from '@server/tests/shared'
7import { areHttpImportTestsDisabled } from '@shared/core-utils'
8import { HttpStatusCode, VideoPrivacy } from '@shared/models'
9import {
10 cleanupTests,
11 createMultipleServers,
12 doubleFollow,
13 makeGetRequest,
14 PeerTubeServer,
15 sendRTMPStream,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 stopFfmpeg,
19 waitJobs
20} from '@shared/server-commands'
21
22async function checkStoryboard (options: {
23 server: PeerTubeServer
24 uuid: string
25 tilesCount?: number
26 minSize?: number
27}) {
28 const { server, uuid, tilesCount, minSize = 1000 } = options
29
30 const { storyboards } = await server.storyboard.list({ id: uuid })
31
32 expect(storyboards).to.have.lengthOf(1)
33
34 const storyboard = storyboards[0]
35
36 expect(storyboard.spriteDuration).to.equal(1)
37 expect(storyboard.spriteHeight).to.equal(108)
38 expect(storyboard.spriteWidth).to.equal(192)
39 expect(storyboard.storyboardPath).to.exist
40
41 if (tilesCount) {
42 expect(storyboard.totalWidth).to.equal(192 * Math.min(tilesCount, 10))
43 expect(storyboard.totalHeight).to.equal(108 * Math.max((tilesCount / 10), 1))
44 }
45
46 const { body } = await makeGetRequest({ url: server.url, path: storyboard.storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
47 expect(body.length).to.be.above(minSize)
48}
49
50describe('Test video storyboard', function () {
51 let servers: PeerTubeServer[]
52
53 let baseUUID: string
54
55 before(async function () {
56 this.timeout(120000)
57
58 servers = await createMultipleServers(2)
59 await setAccessTokensToServers(servers)
60 await setDefaultVideoChannel(servers)
61
62 await doubleFollow(servers[0], servers[1])
63 })
64
65 it('Should generate a storyboard after upload without transcoding', async function () {
66 this.timeout(60000)
67
68 // 5s video
69 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_short.webm' })
70 baseUUID = uuid
71 await waitJobs(servers)
72
73 for (const server of servers) {
74 await checkStoryboard({ server, uuid, tilesCount: 5 })
75 }
76 })
77
78 it('Should generate a storyboard after upload without transcoding with a long video', async function () {
79 this.timeout(60000)
80
81 // 124s video
82 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_very_long_10p.mp4' })
83 await waitJobs(servers)
84
85 for (const server of servers) {
86 await checkStoryboard({ server, uuid, tilesCount: 100 })
87 }
88 })
89
90 it('Should generate a storyboard after upload with transcoding', async function () {
91 this.timeout(60000)
92
93 await servers[0].config.enableMinimumTranscoding()
94
95 // 5s video
96 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_short.webm' })
97 await waitJobs(servers)
98
99 for (const server of servers) {
100 await checkStoryboard({ server, uuid, tilesCount: 5 })
101 }
102 })
103
104 it('Should generate a storyboard after an audio upload', async function () {
105 this.timeout(60000)
106
107 // 6s audio
108 const attributes = { name: 'audio', fixture: 'sample.ogg' }
109 const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' })
110 await waitJobs(servers)
111
112 for (const server of servers) {
113 try {
114 await checkStoryboard({ server, uuid, tilesCount: 6, minSize: 250 })
115 } catch { // FIXME: to remove after ffmpeg CI upgrade, ffmpeg CI version (4.3) generates a 7.6s length video
116 await checkStoryboard({ server, uuid, tilesCount: 8, minSize: 250 })
117 }
118 }
119 })
120
121 it('Should generate a storyboard after HTTP import', async function () {
122 this.timeout(60000)
123
124 if (areHttpImportTestsDisabled()) return
125
126 // 3s video
127 const { video } = await servers[0].imports.importVideo({
128 attributes: {
129 targetUrl: FIXTURE_URLS.goodVideo,
130 channelId: servers[0].store.channel.id,
131 privacy: VideoPrivacy.PUBLIC
132 }
133 })
134 await waitJobs(servers)
135
136 for (const server of servers) {
137 await checkStoryboard({ server, uuid: video.uuid, tilesCount: 3 })
138 }
139 })
140
141 it('Should generate a storyboard after torrent import', async function () {
142 this.timeout(60000)
143
144 if (areHttpImportTestsDisabled()) return
145
146 // 10s video
147 const { video } = await servers[0].imports.importVideo({
148 attributes: {
149 magnetUri: FIXTURE_URLS.magnet,
150 channelId: servers[0].store.channel.id,
151 privacy: VideoPrivacy.PUBLIC
152 }
153 })
154 await waitJobs(servers)
155
156 for (const server of servers) {
157 await checkStoryboard({ server, uuid: video.uuid, tilesCount: 10 })
158 }
159 })
160
161 it('Should generate a storyboard after a live', async function () {
162 this.timeout(240000)
163
164 await servers[0].config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
165
166 const { live, video } = await servers[0].live.quickCreate({
167 saveReplay: true,
168 permanentLive: false,
169 privacy: VideoPrivacy.PUBLIC
170 })
171
172 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
173 await servers[0].live.waitUntilPublished({ videoId: video.id })
174
175 await stopFfmpeg(ffmpegCommand)
176
177 await servers[0].live.waitUntilReplacedByReplay({ videoId: video.id })
178 await waitJobs(servers)
179
180 for (const server of servers) {
181 await checkStoryboard({ server, uuid: video.uuid })
182 }
183 })
184
185 it('Should cleanup storyboards on video deletion', async function () {
186 this.timeout(60000)
187
188 const { storyboards } = await servers[0].storyboard.list({ id: baseUUID })
189 const storyboardName = basename(storyboards[0].storyboardPath)
190
191 const listFiles = () => {
192 const storyboardPath = servers[0].getDirectoryPath('storyboards')
193 return readdir(storyboardPath)
194 }
195
196 {
197 const storyboads = await listFiles()
198 expect(storyboads).to.include(storyboardName)
199 }
200
201 await servers[0].videos.remove({ id: baseUUID })
202 await waitJobs(servers)
203
204 {
205 const storyboads = await listFiles()
206 expect(storyboads).to.not.include(storyboardName)
207 }
208 })
209
210 after(async function () {
211 await cleanupTests(servers)
212 })
213})
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
index 30251706b..73c066bfb 100644
--- a/server/tests/api/videos/videos-common-filters.ts
+++ b/server/tests/api/videos/videos-common-filters.ts
@@ -154,7 +154,7 @@ describe('Test videos filter', function () {
154 server: PeerTubeServer 154 server: PeerTubeServer
155 path: string 155 path: string
156 isLocal?: boolean 156 isLocal?: boolean
157 hasWebtorrentFiles?: boolean 157 hasWebVideoFiles?: boolean
158 hasHLSFiles?: boolean 158 hasHLSFiles?: boolean
159 include?: VideoInclude 159 include?: VideoInclude
160 privacyOneOf?: VideoPrivacy[] 160 privacyOneOf?: VideoPrivacy[]
@@ -174,7 +174,7 @@ describe('Test videos filter', function () {
174 'include', 174 'include',
175 'category', 175 'category',
176 'tagsAllOf', 176 'tagsAllOf',
177 'hasWebtorrentFiles', 177 'hasWebVideoFiles',
178 'hasHLSFiles', 178 'hasHLSFiles',
179 'privacyOneOf', 179 'privacyOneOf',
180 'excludeAlreadyWatched' 180 'excludeAlreadyWatched'
@@ -463,14 +463,14 @@ describe('Test videos filter', function () {
463 } 463 }
464 }) 464 })
465 465
466 it('Should filter by HLS or WebTorrent files', async function () { 466 it('Should filter by HLS or Web Video files', async function () {
467 this.timeout(360000) 467 this.timeout(360000)
468 468
469 const finderFactory = (name: string) => (videos: Video[]) => videos.some(v => v.name === name) 469 const finderFactory = (name: string) => (videos: Video[]) => videos.some(v => v.name === name)
470 470
471 await servers[0].config.enableTranscoding(true, false) 471 await servers[0].config.enableTranscoding(true, false)
472 await servers[0].videos.upload({ attributes: { name: 'webtorrent video' } }) 472 await servers[0].videos.upload({ attributes: { name: 'web video video' } })
473 const hasWebtorrent = finderFactory('webtorrent video') 473 const hasWebVideo = finderFactory('web video video')
474 474
475 await waitJobs(servers) 475 await waitJobs(servers)
476 476
@@ -481,24 +481,24 @@ describe('Test videos filter', function () {
481 await waitJobs(servers) 481 await waitJobs(servers)
482 482
483 await servers[0].config.enableTranscoding(true, true) 483 await servers[0].config.enableTranscoding(true, true)
484 await servers[0].videos.upload({ attributes: { name: 'hls and webtorrent video' } }) 484 await servers[0].videos.upload({ attributes: { name: 'hls and web video video' } })
485 const hasBoth = finderFactory('hls and webtorrent video') 485 const hasBoth = finderFactory('hls and web video video')
486 486
487 await waitJobs(servers) 487 await waitJobs(servers)
488 488
489 for (const path of paths) { 489 for (const path of paths) {
490 { 490 {
491 const videos = await listVideos({ server: servers[0], path, hasWebtorrentFiles: true }) 491 const videos = await listVideos({ server: servers[0], path, hasWebVideoFiles: true })
492 492
493 expect(hasWebtorrent(videos)).to.be.true 493 expect(hasWebVideo(videos)).to.be.true
494 expect(hasHLS(videos)).to.be.false 494 expect(hasHLS(videos)).to.be.false
495 expect(hasBoth(videos)).to.be.true 495 expect(hasBoth(videos)).to.be.true
496 } 496 }
497 497
498 { 498 {
499 const videos = await listVideos({ server: servers[0], path, hasWebtorrentFiles: false }) 499 const videos = await listVideos({ server: servers[0], path, hasWebVideoFiles: false })
500 500
501 expect(hasWebtorrent(videos)).to.be.false 501 expect(hasWebVideo(videos)).to.be.false
502 expect(hasHLS(videos)).to.be.true 502 expect(hasHLS(videos)).to.be.true
503 expect(hasBoth(videos)).to.be.false 503 expect(hasBoth(videos)).to.be.false
504 } 504 }
@@ -506,7 +506,7 @@ describe('Test videos filter', function () {
506 { 506 {
507 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true }) 507 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true })
508 508
509 expect(hasWebtorrent(videos)).to.be.false 509 expect(hasWebVideo(videos)).to.be.false
510 expect(hasHLS(videos)).to.be.true 510 expect(hasHLS(videos)).to.be.true
511 expect(hasBoth(videos)).to.be.true 511 expect(hasBoth(videos)).to.be.true
512 } 512 }
@@ -514,23 +514,23 @@ describe('Test videos filter', function () {
514 { 514 {
515 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false }) 515 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false })
516 516
517 expect(hasWebtorrent(videos)).to.be.true 517 expect(hasWebVideo(videos)).to.be.true
518 expect(hasHLS(videos)).to.be.false 518 expect(hasHLS(videos)).to.be.false
519 expect(hasBoth(videos)).to.be.false 519 expect(hasBoth(videos)).to.be.false
520 } 520 }
521 521
522 { 522 {
523 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false, hasWebtorrentFiles: false }) 523 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false, hasWebVideoFiles: false })
524 524
525 expect(hasWebtorrent(videos)).to.be.false 525 expect(hasWebVideo(videos)).to.be.false
526 expect(hasHLS(videos)).to.be.false 526 expect(hasHLS(videos)).to.be.false
527 expect(hasBoth(videos)).to.be.false 527 expect(hasBoth(videos)).to.be.false
528 } 528 }
529 529
530 { 530 {
531 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true, hasWebtorrentFiles: true }) 531 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true, hasWebVideoFiles: true })
532 532
533 expect(hasWebtorrent(videos)).to.be.false 533 expect(hasWebVideo(videos)).to.be.false
534 expect(hasHLS(videos)).to.be.false 534 expect(hasHLS(videos)).to.be.false
535 expect(hasBoth(videos)).to.be.true 535 expect(hasBoth(videos)).to.be.true
536 } 536 }