aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/tests/api')
-rw-r--r--server/tests/api/check-params/index.ts6
-rw-r--r--server/tests/api/check-params/live.ts17
-rw-r--r--server/tests/api/check-params/two-factor.ts288
-rw-r--r--server/tests/api/check-params/video-files.ts217
-rw-r--r--server/tests/api/check-params/video-token.ts44
-rw-r--r--server/tests/api/live/live-constraints.ts2
-rw-r--r--server/tests/api/live/live-fast-restream.ts29
-rw-r--r--server/tests/api/live/live-permanent.ts12
-rw-r--r--server/tests/api/live/live-save-replay.ts18
-rw-r--r--server/tests/api/live/live.ts116
-rw-r--r--server/tests/api/notifications/admin-notifications.ts6
-rw-r--r--server/tests/api/notifications/moderation-notifications.ts4
-rw-r--r--server/tests/api/object-storage/index.ts1
-rw-r--r--server/tests/api/object-storage/live.ts191
-rw-r--r--server/tests/api/object-storage/video-imports.ts20
-rw-r--r--server/tests/api/object-storage/video-static-file-privacy.ts402
-rw-r--r--server/tests/api/object-storage/videos.ts60
-rw-r--r--server/tests/api/redundancy/redundancy.ts22
-rw-r--r--server/tests/api/server/follow-constraints.ts88
-rw-r--r--server/tests/api/server/open-telemetry.ts6
-rw-r--r--server/tests/api/server/proxy.ts16
-rw-r--r--server/tests/api/transcoding/audio-only.ts7
-rw-r--r--server/tests/api/transcoding/create-transcoding.ts26
-rw-r--r--server/tests/api/transcoding/hls.ts171
-rw-r--r--server/tests/api/transcoding/index.ts1
-rw-r--r--server/tests/api/transcoding/update-while-transcoding.ts151
-rw-r--r--server/tests/api/transcoding/video-studio.ts12
-rw-r--r--server/tests/api/users/index.ts1
-rw-r--r--server/tests/api/users/two-factor.ts200
-rw-r--r--server/tests/api/users/users-multiple-servers.ts2
-rw-r--r--server/tests/api/users/users.ts8
-rw-r--r--server/tests/api/videos/channel-import-videos.ts43
-rw-r--r--server/tests/api/videos/index.ts1
-rw-r--r--server/tests/api/videos/multiple-servers.ts2
-rw-r--r--server/tests/api/videos/video-channel-syncs.ts2
-rw-r--r--server/tests/api/videos/video-description.ts23
-rw-r--r--server/tests/api/videos/video-files.ts4
-rw-r--r--server/tests/api/videos/video-playlists.ts16
-rw-r--r--server/tests/api/videos/video-privacy.ts4
-rw-r--r--server/tests/api/videos/video-static-file-privacy.ts422
-rw-r--r--server/tests/api/videos/videos-common-filters.ts2
-rw-r--r--server/tests/api/views/video-views-counter.ts2
-rw-r--r--server/tests/api/views/video-views-overall-stats.ts4
-rw-r--r--server/tests/api/views/video-views-retention-stats.ts2
-rw-r--r--server/tests/api/views/video-views-timeserie-stats.ts4
45 files changed, 2190 insertions, 485 deletions
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts
index cd7a38459..961093bb5 100644
--- a/server/tests/api/check-params/index.ts
+++ b/server/tests/api/check-params/index.ts
@@ -2,6 +2,7 @@ import './abuses'
2import './accounts' 2import './accounts'
3import './blocklist' 3import './blocklist'
4import './bulk' 4import './bulk'
5import './channel-import-videos'
5import './config' 6import './config'
6import './contact-form' 7import './contact-form'
7import './custom-pages' 8import './custom-pages'
@@ -17,6 +18,7 @@ import './redundancy'
17import './search' 18import './search'
18import './services' 19import './services'
19import './transcoding' 20import './transcoding'
21import './two-factor'
20import './upload-quota' 22import './upload-quota'
21import './user-notifications' 23import './user-notifications'
22import './user-subscriptions' 24import './user-subscriptions'
@@ -24,15 +26,15 @@ import './users-admin'
24import './users' 26import './users'
25import './video-blacklist' 27import './video-blacklist'
26import './video-captions' 28import './video-captions'
29import './video-channel-syncs'
27import './video-channels' 30import './video-channels'
28import './video-comments' 31import './video-comments'
29import './video-files' 32import './video-files'
30import './video-imports' 33import './video-imports'
31import './video-channel-syncs'
32import './channel-import-videos'
33import './video-playlists' 34import './video-playlists'
34import './video-source' 35import './video-source'
35import './video-studio' 36import './video-studio'
37import './video-token'
36import './videos-common-filters' 38import './videos-common-filters'
37import './videos-history' 39import './videos-history'
38import './videos-overviews' 40import './videos-overviews'
diff --git a/server/tests/api/check-params/live.ts b/server/tests/api/check-params/live.ts
index 3f553c42b..2eff9414b 100644
--- a/server/tests/api/check-params/live.ts
+++ b/server/tests/api/check-params/live.ts
@@ -502,6 +502,23 @@ describe('Test video lives API validator', function () {
502 await stopFfmpeg(ffmpegCommand) 502 await stopFfmpeg(ffmpegCommand)
503 }) 503 })
504 504
505 it('Should fail to change live privacy if it has already started', async function () {
506 this.timeout(40000)
507
508 const live = await command.get({ videoId: video.id })
509
510 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
511
512 await command.waitUntilPublished({ videoId: video.id })
513 await server.videos.update({
514 id: video.id,
515 attributes: { privacy: VideoPrivacy.PUBLIC },
516 expectedStatus: HttpStatusCode.BAD_REQUEST_400
517 })
518
519 await stopFfmpeg(ffmpegCommand)
520 })
521
505 it('Should fail to stream twice in the save live', async function () { 522 it('Should fail to stream twice in the save live', async function () {
506 this.timeout(40000) 523 this.timeout(40000)
507 524
diff --git a/server/tests/api/check-params/two-factor.ts b/server/tests/api/check-params/two-factor.ts
new file mode 100644
index 000000000..f8365f1b5
--- /dev/null
+++ b/server/tests/api/check-params/two-factor.ts
@@ -0,0 +1,288 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@shared/models'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, TwoFactorCommand } from '@shared/server-commands'
5
6describe('Test two factor API validators', function () {
7 let server: PeerTubeServer
8
9 let rootId: number
10 let rootPassword: string
11 let rootRequestToken: string
12 let rootOTPToken: string
13
14 let userId: number
15 let userToken = ''
16 let userPassword: string
17 let userRequestToken: string
18 let userOTPToken: string
19
20 // ---------------------------------------------------------------
21
22 before(async function () {
23 this.timeout(30000)
24
25 {
26 server = await createSingleServer(1)
27 await setAccessTokensToServers([ server ])
28 }
29
30 {
31 const result = await server.users.generate('user1')
32 userToken = result.token
33 userId = result.userId
34 userPassword = result.password
35 }
36
37 {
38 const { id } = await server.users.getMyInfo()
39 rootId = id
40 rootPassword = server.store.user.password
41 }
42 })
43
44 describe('When requesting two factor', function () {
45
46 it('Should fail with an unknown user id', async function () {
47 await server.twoFactor.request({ userId: 42, currentPassword: rootPassword, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
48 })
49
50 it('Should fail with an invalid user id', async function () {
51 await server.twoFactor.request({
52 userId: 'invalid' as any,
53 currentPassword: rootPassword,
54 expectedStatus: HttpStatusCode.BAD_REQUEST_400
55 })
56 })
57
58 it('Should fail to request another user two factor without the appropriate rights', async function () {
59 await server.twoFactor.request({
60 userId: rootId,
61 token: userToken,
62 currentPassword: userPassword,
63 expectedStatus: HttpStatusCode.FORBIDDEN_403
64 })
65 })
66
67 it('Should succeed to request another user two factor with the appropriate rights', async function () {
68 await server.twoFactor.request({ userId, currentPassword: rootPassword })
69 })
70
71 it('Should fail to request two factor without a password', async function () {
72 await server.twoFactor.request({
73 userId,
74 token: userToken,
75 currentPassword: undefined,
76 expectedStatus: HttpStatusCode.BAD_REQUEST_400
77 })
78 })
79
80 it('Should fail to request two factor with an incorrect password', async function () {
81 await server.twoFactor.request({
82 userId,
83 token: userToken,
84 currentPassword: rootPassword,
85 expectedStatus: HttpStatusCode.FORBIDDEN_403
86 })
87 })
88
89 it('Should succeed to request two factor without a password when targeting a remote user with an admin account', async function () {
90 await server.twoFactor.request({ userId })
91 })
92
93 it('Should fail to request two factor without a password when targeting myself with an admin account', async function () {
94 await server.twoFactor.request({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
95 await server.twoFactor.request({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
96 })
97
98 it('Should succeed to request my two factor auth', async function () {
99 {
100 const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword })
101 userRequestToken = otpRequest.requestToken
102 userOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
103 }
104
105 {
106 const { otpRequest } = await server.twoFactor.request({ userId: rootId, currentPassword: rootPassword })
107 rootRequestToken = otpRequest.requestToken
108 rootOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
109 }
110 })
111 })
112
113 describe('When confirming two factor request', function () {
114
115 it('Should fail with an unknown user id', async function () {
116 await server.twoFactor.confirmRequest({
117 userId: 42,
118 requestToken: rootRequestToken,
119 otpToken: rootOTPToken,
120 expectedStatus: HttpStatusCode.NOT_FOUND_404
121 })
122 })
123
124 it('Should fail with an invalid user id', async function () {
125 await server.twoFactor.confirmRequest({
126 userId: 'invalid' as any,
127 requestToken: rootRequestToken,
128 otpToken: rootOTPToken,
129 expectedStatus: HttpStatusCode.BAD_REQUEST_400
130 })
131 })
132
133 it('Should fail to confirm another user two factor request without the appropriate rights', async function () {
134 await server.twoFactor.confirmRequest({
135 userId: rootId,
136 token: userToken,
137 requestToken: rootRequestToken,
138 otpToken: rootOTPToken,
139 expectedStatus: HttpStatusCode.FORBIDDEN_403
140 })
141 })
142
143 it('Should fail without request token', async function () {
144 await server.twoFactor.confirmRequest({
145 userId,
146 requestToken: undefined,
147 otpToken: userOTPToken,
148 expectedStatus: HttpStatusCode.BAD_REQUEST_400
149 })
150 })
151
152 it('Should fail with an invalid request token', async function () {
153 await server.twoFactor.confirmRequest({
154 userId,
155 requestToken: 'toto',
156 otpToken: userOTPToken,
157 expectedStatus: HttpStatusCode.FORBIDDEN_403
158 })
159 })
160
161 it('Should fail with request token of another user', async function () {
162 await server.twoFactor.confirmRequest({
163 userId,
164 requestToken: rootRequestToken,
165 otpToken: userOTPToken,
166 expectedStatus: HttpStatusCode.FORBIDDEN_403
167 })
168 })
169
170 it('Should fail without an otp token', async function () {
171 await server.twoFactor.confirmRequest({
172 userId,
173 requestToken: userRequestToken,
174 otpToken: undefined,
175 expectedStatus: HttpStatusCode.BAD_REQUEST_400
176 })
177 })
178
179 it('Should fail with a bad otp token', async function () {
180 await server.twoFactor.confirmRequest({
181 userId,
182 requestToken: userRequestToken,
183 otpToken: '123456',
184 expectedStatus: HttpStatusCode.FORBIDDEN_403
185 })
186 })
187
188 it('Should succeed to confirm another user two factor request with the appropriate rights', async function () {
189 await server.twoFactor.confirmRequest({
190 userId,
191 requestToken: userRequestToken,
192 otpToken: userOTPToken
193 })
194
195 // Reinit
196 await server.twoFactor.disable({ userId, currentPassword: rootPassword })
197 })
198
199 it('Should succeed to confirm my two factor request', async function () {
200 await server.twoFactor.confirmRequest({
201 userId,
202 token: userToken,
203 requestToken: userRequestToken,
204 otpToken: userOTPToken
205 })
206 })
207
208 it('Should fail to confirm again two factor request', async function () {
209 await server.twoFactor.confirmRequest({
210 userId,
211 token: userToken,
212 requestToken: userRequestToken,
213 otpToken: userOTPToken,
214 expectedStatus: HttpStatusCode.BAD_REQUEST_400
215 })
216 })
217 })
218
219 describe('When disabling two factor', function () {
220
221 it('Should fail with an unknown user id', async function () {
222 await server.twoFactor.disable({
223 userId: 42,
224 currentPassword: rootPassword,
225 expectedStatus: HttpStatusCode.NOT_FOUND_404
226 })
227 })
228
229 it('Should fail with an invalid user id', async function () {
230 await server.twoFactor.disable({
231 userId: 'invalid' as any,
232 currentPassword: rootPassword,
233 expectedStatus: HttpStatusCode.BAD_REQUEST_400
234 })
235 })
236
237 it('Should fail to disable another user two factor without the appropriate rights', async function () {
238 await server.twoFactor.disable({
239 userId: rootId,
240 token: userToken,
241 currentPassword: userPassword,
242 expectedStatus: HttpStatusCode.FORBIDDEN_403
243 })
244 })
245
246 it('Should fail to disable two factor with an incorrect password', async function () {
247 await server.twoFactor.disable({
248 userId,
249 token: userToken,
250 currentPassword: rootPassword,
251 expectedStatus: HttpStatusCode.FORBIDDEN_403
252 })
253 })
254
255 it('Should succeed to disable two factor without a password when targeting a remote user with an admin account', async function () {
256 await server.twoFactor.disable({ userId })
257 await server.twoFactor.requestAndConfirm({ userId })
258 })
259
260 it('Should fail to disable two factor without a password when targeting myself with an admin account', async function () {
261 await server.twoFactor.disable({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
262 await server.twoFactor.disable({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
263 })
264
265 it('Should succeed to disable another user two factor with the appropriate rights', async function () {
266 await server.twoFactor.disable({ userId, currentPassword: rootPassword })
267
268 await server.twoFactor.requestAndConfirm({ userId })
269 })
270
271 it('Should succeed to update my two factor auth', async function () {
272 await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword })
273 })
274
275 it('Should fail to disable again two factor', async function () {
276 await server.twoFactor.disable({
277 userId,
278 token: userToken,
279 currentPassword: userPassword,
280 expectedStatus: HttpStatusCode.BAD_REQUEST_400
281 })
282 })
283 })
284
285 after(async function () {
286 await cleanupTests([ server ])
287 })
288})
diff --git a/server/tests/api/check-params/video-files.ts b/server/tests/api/check-params/video-files.ts
index aa4de2c83..9dc59a1b5 100644
--- a/server/tests/api/check-params/video-files.ts
+++ b/server/tests/api/check-params/video-files.ts
@@ -1,10 +1,12 @@
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 { HttpStatusCode, UserRole } from '@shared/models' 3import { getAllFiles } from '@shared/core-utils'
4import { HttpStatusCode, UserRole, VideoDetails, VideoPrivacy } from '@shared/models'
4import { 5import {
5 cleanupTests, 6 cleanupTests,
6 createMultipleServers, 7 createMultipleServers,
7 doubleFollow, 8 doubleFollow,
9 makeRawRequest,
8 PeerTubeServer, 10 PeerTubeServer,
9 setAccessTokensToServers, 11 setAccessTokensToServers,
10 waitJobs 12 waitJobs
@@ -13,22 +15,9 @@ import {
13describe('Test videos files', function () { 15describe('Test videos files', function () {
14 let servers: PeerTubeServer[] 16 let servers: PeerTubeServer[]
15 17
16 let webtorrentId: string
17 let hlsId: string
18 let remoteId: string
19
20 let userToken: string 18 let userToken: string
21 let moderatorToken: string 19 let moderatorToken: string
22 20
23 let validId1: string
24 let validId2: string
25
26 let hlsFileId: number
27 let webtorrentFileId: number
28
29 let remoteHLSFileId: number
30 let remoteWebtorrentFileId: number
31
32 // --------------------------------------------------------------- 21 // ---------------------------------------------------------------
33 22
34 before(async function () { 23 before(async function () {
@@ -41,117 +30,163 @@ describe('Test videos files', function () {
41 30
42 userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER) 31 userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
43 moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR) 32 moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
33 })
44 34
45 { 35 describe('Getting metadata', function () {
46 const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' }) 36 let video: VideoDetails
47 await waitJobs(servers) 37
38 before(async function () {
39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
40 video = await servers[0].videos.getWithToken({ id: uuid })
41 })
42
43 it('Should not get metadata of private video without token', async function () {
44 for (const file of getAllFiles(video)) {
45 await makeRawRequest({ url: file.metadataUrl, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
46 }
47 })
48
49 it('Should not get metadata of private video without the appropriate token', async function () {
50 for (const file of getAllFiles(video)) {
51 await makeRawRequest({ url: file.metadataUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
52 }
53 })
54
55 it('Should get metadata of private video with the appropriate token', async function () {
56 for (const file of getAllFiles(video)) {
57 await makeRawRequest({ url: file.metadataUrl, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 })
58 }
59 })
60 })
61
62 describe('Deleting files', function () {
63 let webtorrentId: string
64 let hlsId: string
65 let remoteId: string
66
67 let validId1: string
68 let validId2: string
48 69
49 const video = await servers[1].videos.get({ id: uuid }) 70 let hlsFileId: number
50 remoteId = video.uuid 71 let webtorrentFileId: number
51 remoteHLSFileId = video.streamingPlaylists[0].files[0].id
52 remoteWebtorrentFileId = video.files[0].id
53 }
54 72
55 { 73 let remoteHLSFileId: number
56 await servers[0].config.enableTranscoding(true, true) 74 let remoteWebtorrentFileId: number
75
76 before(async function () {
77 this.timeout(300_000)
57 78
58 { 79 {
59 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' }) 80 const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
60 await waitJobs(servers) 81 await waitJobs(servers)
61 82
62 const video = await servers[0].videos.get({ id: uuid }) 83 const video = await servers[1].videos.get({ id: uuid })
63 validId1 = video.uuid 84 remoteId = video.uuid
64 hlsFileId = video.streamingPlaylists[0].files[0].id 85 remoteHLSFileId = video.streamingPlaylists[0].files[0].id
65 webtorrentFileId = video.files[0].id 86 remoteWebtorrentFileId = video.files[0].id
66 } 87 }
67 88
68 { 89 {
69 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 2' }) 90 await servers[0].config.enableTranscoding(true, true)
70 validId2 = uuid 91
92 {
93 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' })
94 await waitJobs(servers)
95
96 const video = await servers[0].videos.get({ id: uuid })
97 validId1 = video.uuid
98 hlsFileId = video.streamingPlaylists[0].files[0].id
99 webtorrentFileId = video.files[0].id
100 }
101
102 {
103 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 2' })
104 validId2 = uuid
105 }
71 } 106 }
72 }
73 107
74 await waitJobs(servers) 108 await waitJobs(servers)
75 109
76 { 110 {
77 await servers[0].config.enableTranscoding(false, true) 111 await servers[0].config.enableTranscoding(false, true)
78 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls' }) 112 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls' })
79 hlsId = uuid 113 hlsId = uuid
80 } 114 }
81 115
82 await waitJobs(servers) 116 await waitJobs(servers)
83 117
84 { 118 {
85 await servers[0].config.enableTranscoding(false, true) 119 await servers[0].config.enableTranscoding(false, true)
86 const { uuid } = await servers[0].videos.quickUpload({ name: 'webtorrent' }) 120 const { uuid } = await servers[0].videos.quickUpload({ name: 'webtorrent' })
87 webtorrentId = uuid 121 webtorrentId = uuid
88 } 122 }
89 123
90 await waitJobs(servers) 124 await waitJobs(servers)
91 }) 125 })
92 126
93 it('Should not delete files of a unknown video', async function () { 127 it('Should not delete files of a unknown video', async function () {
94 const expectedStatus = HttpStatusCode.NOT_FOUND_404 128 const expectedStatus = HttpStatusCode.NOT_FOUND_404
95 129
96 await servers[0].videos.removeHLSPlaylist({ videoId: 404, expectedStatus }) 130 await servers[0].videos.removeHLSPlaylist({ videoId: 404, expectedStatus })
97 await servers[0].videos.removeAllWebTorrentFiles({ videoId: 404, expectedStatus }) 131 await servers[0].videos.removeAllWebTorrentFiles({ videoId: 404, expectedStatus })
98 132
99 await servers[0].videos.removeHLSFile({ videoId: 404, fileId: hlsFileId, expectedStatus }) 133 await servers[0].videos.removeHLSFile({ videoId: 404, fileId: hlsFileId, expectedStatus })
100 await servers[0].videos.removeWebTorrentFile({ videoId: 404, fileId: webtorrentFileId, expectedStatus }) 134 await servers[0].videos.removeWebTorrentFile({ videoId: 404, fileId: webtorrentFileId, expectedStatus })
101 }) 135 })
102 136
103 it('Should not delete unknown files', async function () { 137 it('Should not delete unknown files', async function () {
104 const expectedStatus = HttpStatusCode.NOT_FOUND_404 138 const expectedStatus = HttpStatusCode.NOT_FOUND_404
105 139
106 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: webtorrentFileId, expectedStatus }) 140 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: webtorrentFileId, expectedStatus })
107 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: hlsFileId, expectedStatus }) 141 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: hlsFileId, expectedStatus })
108 }) 142 })
109 143
110 it('Should not delete files of a remote video', async function () { 144 it('Should not delete files of a remote video', async function () {
111 const expectedStatus = HttpStatusCode.BAD_REQUEST_400 145 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
112 146
113 await servers[0].videos.removeHLSPlaylist({ videoId: remoteId, expectedStatus }) 147 await servers[0].videos.removeHLSPlaylist({ videoId: remoteId, expectedStatus })
114 await servers[0].videos.removeAllWebTorrentFiles({ videoId: remoteId, expectedStatus }) 148 await servers[0].videos.removeAllWebTorrentFiles({ videoId: remoteId, expectedStatus })
115 149
116 await servers[0].videos.removeHLSFile({ videoId: remoteId, fileId: remoteHLSFileId, expectedStatus }) 150 await servers[0].videos.removeHLSFile({ videoId: remoteId, fileId: remoteHLSFileId, expectedStatus })
117 await servers[0].videos.removeWebTorrentFile({ videoId: remoteId, fileId: remoteWebtorrentFileId, expectedStatus }) 151 await servers[0].videos.removeWebTorrentFile({ videoId: remoteId, fileId: remoteWebtorrentFileId, expectedStatus })
118 }) 152 })
119 153
120 it('Should not delete files by a non admin user', async function () { 154 it('Should not delete files by a non admin user', async function () {
121 const expectedStatus = HttpStatusCode.FORBIDDEN_403 155 const expectedStatus = HttpStatusCode.FORBIDDEN_403
122 156
123 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: userToken, expectedStatus }) 157 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: userToken, expectedStatus })
124 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: moderatorToken, expectedStatus }) 158 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: moderatorToken, expectedStatus })
125 159
126 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId1, token: userToken, expectedStatus }) 160 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId1, token: userToken, expectedStatus })
127 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId1, token: moderatorToken, expectedStatus }) 161 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId1, token: moderatorToken, expectedStatus })
128 162
129 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: userToken, expectedStatus }) 163 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: userToken, expectedStatus })
130 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: moderatorToken, expectedStatus }) 164 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: moderatorToken, expectedStatus })
131 165
132 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId, token: userToken, expectedStatus }) 166 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId, token: userToken, expectedStatus })
133 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId, token: moderatorToken, expectedStatus }) 167 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId, token: moderatorToken, expectedStatus })
134 }) 168 })
135 169
136 it('Should not delete files if the files are not available', async function () { 170 it('Should not delete files if the files are not available', async function () {
137 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 171 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
138 await servers[0].videos.removeAllWebTorrentFiles({ videoId: webtorrentId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 172 await servers[0].videos.removeAllWebTorrentFiles({ videoId: webtorrentId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
139 173
140 await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 174 await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
141 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 175 await servers[0].videos.removeWebTorrentFile({ videoId: webtorrentId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
142 }) 176 })
143 177
144 it('Should not delete files if no both versions are available', async function () { 178 it('Should not delete files if no both versions are available', async function () {
145 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 179 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
146 await servers[0].videos.removeAllWebTorrentFiles({ videoId: webtorrentId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 180 await servers[0].videos.removeAllWebTorrentFiles({ videoId: webtorrentId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
147 }) 181 })
148 182
149 it('Should delete files if both versions are available', async function () { 183 it('Should delete files if both versions are available', async function () {
150 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId }) 184 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId })
151 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId }) 185 await servers[0].videos.removeWebTorrentFile({ videoId: validId1, fileId: webtorrentFileId })
152 186
153 await servers[0].videos.removeHLSPlaylist({ videoId: validId1 }) 187 await servers[0].videos.removeHLSPlaylist({ videoId: validId1 })
154 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId2 }) 188 await servers[0].videos.removeAllWebTorrentFiles({ videoId: validId2 })
189 })
155 }) 190 })
156 191
157 after(async function () { 192 after(async function () {
diff --git a/server/tests/api/check-params/video-token.ts b/server/tests/api/check-params/video-token.ts
new file mode 100644
index 000000000..7acb9d580
--- /dev/null
+++ b/server/tests/api/check-params/video-token.ts
@@ -0,0 +1,44 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, VideoPrivacy } from '@shared/models'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5
6describe('Test video tokens', function () {
7 let server: PeerTubeServer
8 let videoId: string
9 let userToken: string
10
11 // ---------------------------------------------------------------
12
13 before(async function () {
14 this.timeout(300_000)
15
16 server = await createSingleServer(1)
17 await setAccessTokensToServers([ server ])
18
19 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
20 videoId = uuid
21
22 userToken = await server.users.generateUserAndToken('user1')
23 })
24
25 it('Should not generate tokens for unauthenticated user', async function () {
26 await server.videoToken.create({ videoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
27 })
28
29 it('Should not generate tokens of unknown video', async function () {
30 await server.videoToken.create({ videoId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
31 })
32
33 it('Should not generate tokens of a non owned video', async function () {
34 await server.videoToken.create({ videoId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
35 })
36
37 it('Should generate token', async function () {
38 await server.videoToken.create({ videoId })
39 })
40
41 after(async function () {
42 await cleanupTests([ server ])
43 })
44})
diff --git a/server/tests/api/live/live-constraints.ts b/server/tests/api/live/live-constraints.ts
index 64ef73a2a..c82585a9e 100644
--- a/server/tests/api/live/live-constraints.ts
+++ b/server/tests/api/live/live-constraints.ts
@@ -49,7 +49,7 @@ describe('Test live constraints', function () {
49 expect(video.duration).to.be.greaterThan(0) 49 expect(video.duration).to.be.greaterThan(0)
50 } 50 }
51 51
52 await checkLiveCleanup(servers[0], videoId, resolutions) 52 await checkLiveCleanup({ server: servers[0], permanent: false, videoUUID: videoId, savedResolutions: resolutions })
53 } 53 }
54 54
55 function updateQuota (options: { total: number, daily: number }) { 55 function updateQuota (options: { total: number, daily: number }) {
diff --git a/server/tests/api/live/live-fast-restream.ts b/server/tests/api/live/live-fast-restream.ts
index 502959258..c0bb8d529 100644
--- a/server/tests/api/live/live-fast-restream.ts
+++ b/server/tests/api/live/live-fast-restream.ts
@@ -43,12 +43,31 @@ describe('Fast restream in live', function () {
43 // Streaming session #1 43 // Streaming session #1
44 let ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions) 44 let ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
45 await server.live.waitUntilPublished({ videoId: liveVideoUUID }) 45 await server.live.waitUntilPublished({ videoId: liveVideoUUID })
46
47 const video = await server.videos.get({ id: liveVideoUUID })
48 const session1PlaylistId = video.streamingPlaylists[0].id
49
46 await stopFfmpeg(ffmpegCommand) 50 await stopFfmpeg(ffmpegCommand)
47 await server.live.waitUntilWaiting({ videoId: liveVideoUUID }) 51 await server.live.waitUntilWaiting({ videoId: liveVideoUUID })
48 52
49 // Streaming session #2 53 // Streaming session #2
50 ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions) 54 ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
51 await server.live.waitUntilSegmentGeneration({ videoUUID: liveVideoUUID, segment: 0, playlistNumber: 0, totalSessions: 2 }) 55
56 let hasNewPlaylist = false
57 do {
58 const video = await server.videos.get({ id: liveVideoUUID })
59 hasNewPlaylist = video.streamingPlaylists.length === 1 && video.streamingPlaylists[0].id !== session1PlaylistId
60
61 await wait(100)
62 } while (!hasNewPlaylist)
63
64 await server.live.waitUntilSegmentGeneration({
65 server,
66 videoUUID: liveVideoUUID,
67 segment: 1,
68 playlistNumber: 0,
69 objectStorage: false
70 })
52 71
53 return { ffmpegCommand, liveVideoUUID } 72 return { ffmpegCommand, liveVideoUUID }
54 } 73 }
@@ -59,9 +78,9 @@ describe('Fast restream in live', function () {
59 const video = await server.videos.get({ id: liveId }) 78 const video = await server.videos.get({ id: liveId })
60 expect(video.streamingPlaylists).to.have.lengthOf(1) 79 expect(video.streamingPlaylists).to.have.lengthOf(1)
61 80
62 await server.live.getSegment({ videoUUID: liveId, segment: 0, playlistNumber: 0 }) 81 await server.live.getSegmentFile({ videoUUID: liveId, segment: 0, playlistNumber: 0 })
63 await makeRawRequest(video.streamingPlaylists[0].playlistUrl, HttpStatusCode.OK_200) 82 await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
64 await makeRawRequest(video.streamingPlaylists[0].segmentsSha256Url, HttpStatusCode.OK_200) 83 await makeRawRequest({ url: video.streamingPlaylists[0].segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
65 84
66 await wait(100) 85 await wait(100)
67 } 86 }
@@ -111,7 +130,7 @@ describe('Fast restream in live', function () {
111 }) 130 })
112 131
113 it('Should correctly fast reastream in a permanent live with and without save replay', async function () { 132 it('Should correctly fast reastream in a permanent live with and without save replay', async function () {
114 this.timeout(240000) 133 this.timeout(480000)
115 134
116 // A test can take a long time, so prefer to run them in parallel 135 // A test can take a long time, so prefer to run them in parallel
117 await Promise.all([ 136 await Promise.all([
diff --git a/server/tests/api/live/live-permanent.ts b/server/tests/api/live/live-permanent.ts
index 5d227200e..4203b1bfc 100644
--- a/server/tests/api/live/live-permanent.ts
+++ b/server/tests/api/live/live-permanent.ts
@@ -1,6 +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 { checkLiveCleanup } from '@server/tests/shared'
4import { wait } from '@shared/core-utils' 5import { wait } from '@shared/core-utils'
5import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models' 6import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models'
6import { 7import {
@@ -129,6 +130,8 @@ describe('Permanent live', function () {
129 130
130 expect(videoDetails.streamingPlaylists).to.have.lengthOf(0) 131 expect(videoDetails.streamingPlaylists).to.have.lengthOf(0)
131 } 132 }
133
134 await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
132 }) 135 })
133 136
134 it('Should have set this live to waiting for live state', async function () { 137 it('Should have set this live to waiting for live state', async function () {
@@ -186,6 +189,15 @@ describe('Permanent live', function () {
186 } 189 }
187 }) 190 })
188 191
192 it('Should remove the live and have cleaned up the directory', async function () {
193 this.timeout(60000)
194
195 await servers[0].videos.remove({ id: videoUUID })
196 await waitJobs(servers)
197
198 await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
199 })
200
189 after(async function () { 201 after(async function () {
190 await cleanupTests(servers) 202 await cleanupTests(servers)
191 }) 203 })
diff --git a/server/tests/api/live/live-save-replay.ts b/server/tests/api/live/live-save-replay.ts
index 7014292d0..8f17b4566 100644
--- a/server/tests/api/live/live-save-replay.ts
+++ b/server/tests/api/live/live-save-replay.ts
@@ -186,7 +186,7 @@ describe('Save replay setting', function () {
186 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED) 186 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
187 187
188 // No resolutions saved since we did not save replay 188 // No resolutions saved since we did not save replay
189 await checkLiveCleanup(servers[0], liveVideoUUID, []) 189 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
190 }) 190 })
191 191
192 it('Should have appropriate ended session', async function () { 192 it('Should have appropriate ended session', async function () {
@@ -220,7 +220,7 @@ describe('Save replay setting', function () {
220 220
221 await wait(5000) 221 await wait(5000)
222 await waitJobs(servers) 222 await waitJobs(servers)
223 await checkLiveCleanup(servers[0], liveVideoUUID, []) 223 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
224 }) 224 })
225 225
226 it('Should have blacklisted session error', async function () { 226 it('Should have blacklisted session error', async function () {
@@ -238,7 +238,7 @@ describe('Save replay setting', function () {
238 await publishLiveAndDelete({ permanent: false, replay: false }) 238 await publishLiveAndDelete({ permanent: false, replay: false })
239 239
240 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404) 240 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
241 await checkLiveCleanup(servers[0], liveVideoUUID, []) 241 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
242 }) 242 })
243 }) 243 })
244 244
@@ -317,7 +317,7 @@ describe('Save replay setting', function () {
317 }) 317 })
318 318
319 it('Should have cleaned up the live files', async function () { 319 it('Should have cleaned up the live files', async function () {
320 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ]) 320 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
321 }) 321 })
322 322
323 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () { 323 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
@@ -332,7 +332,7 @@ describe('Save replay setting', function () {
332 332
333 await wait(5000) 333 await wait(5000)
334 await waitJobs(servers) 334 await waitJobs(servers)
335 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ]) 335 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
336 }) 336 })
337 337
338 it('Should correctly terminate the stream on delete and delete the video', async function () { 338 it('Should correctly terminate the stream on delete and delete the video', async function () {
@@ -341,7 +341,7 @@ describe('Save replay setting', function () {
341 await publishLiveAndDelete({ permanent: false, replay: true }) 341 await publishLiveAndDelete({ permanent: false, replay: true })
342 342
343 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404) 343 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
344 await checkLiveCleanup(servers[0], liveVideoUUID, []) 344 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
345 }) 345 })
346 }) 346 })
347 347
@@ -413,7 +413,7 @@ describe('Save replay setting', function () {
413 }) 413 })
414 414
415 it('Should have cleaned up the live files', async function () { 415 it('Should have cleaned up the live files', async function () {
416 await checkLiveCleanup(servers[0], liveVideoUUID, []) 416 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
417 }) 417 })
418 418
419 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () { 419 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
@@ -432,7 +432,7 @@ describe('Save replay setting', function () {
432 await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 432 await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
433 } 433 }
434 434
435 await checkLiveCleanup(servers[0], liveVideoUUID, []) 435 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
436 }) 436 })
437 437
438 it('Should correctly terminate the stream on delete and not save the video', async function () { 438 it('Should correctly terminate the stream on delete and not save the video', async function () {
@@ -444,7 +444,7 @@ describe('Save replay setting', function () {
444 expect(replay).to.not.exist 444 expect(replay).to.not.exist
445 445
446 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404) 446 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
447 await checkLiveCleanup(servers[0], liveVideoUUID, []) 447 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
448 }) 448 })
449 }) 449 })
450 450
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index c436f0f01..003cc934f 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -3,7 +3,7 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { basename, join } from 'path' 4import { basename, join } from 'path'
5import { ffprobePromise, getVideoStream } from '@server/helpers/ffmpeg' 5import { ffprobePromise, getVideoStream } from '@server/helpers/ffmpeg'
6import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist, testImage } from '@server/tests/shared' 6import { testImage, testVideoResolutions } from '@server/tests/shared'
7import { getAllFiles, wait } from '@shared/core-utils' 7import { getAllFiles, wait } from '@shared/core-utils'
8import { 8import {
9 HttpStatusCode, 9 HttpStatusCode,
@@ -21,6 +21,7 @@ import {
21 doubleFollow, 21 doubleFollow,
22 killallServers, 22 killallServers,
23 LiveCommand, 23 LiveCommand,
24 makeGetRequest,
24 makeRawRequest, 25 makeRawRequest,
25 PeerTubeServer, 26 PeerTubeServer,
26 sendRTMPStream, 27 sendRTMPStream,
@@ -157,8 +158,8 @@ describe('Test live', function () {
157 expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED) 158 expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED)
158 expect(video.nsfw).to.be.true 159 expect(video.nsfw).to.be.true
159 160
160 await makeRawRequest(server.url + video.thumbnailPath, HttpStatusCode.OK_200) 161 await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
161 await makeRawRequest(server.url + video.previewPath, HttpStatusCode.OK_200) 162 await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
162 } 163 }
163 }) 164 })
164 165
@@ -372,46 +373,6 @@ describe('Test live', function () {
372 return uuid 373 return uuid
373 } 374 }
374 375
375 async function testVideoResolutions (liveVideoId: string, resolutions: number[]) {
376 for (const server of servers) {
377 const { data } = await server.videos.list()
378 expect(data.find(v => v.uuid === liveVideoId)).to.exist
379
380 const video = await server.videos.get({ id: liveVideoId })
381
382 expect(video.streamingPlaylists).to.have.lengthOf(1)
383
384 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
385 expect(hlsPlaylist).to.exist
386
387 // Only finite files are displayed
388 expect(hlsPlaylist.files).to.have.lengthOf(0)
389
390 await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
391
392 for (let i = 0; i < resolutions.length; i++) {
393 const segmentNum = 3
394 const segmentName = `${i}-00000${segmentNum}.ts`
395 await commands[0].waitUntilSegmentGeneration({ videoUUID: video.uuid, playlistNumber: i, segment: segmentNum })
396
397 const subPlaylist = await servers[0].streamingPlaylists.get({
398 url: `${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8`
399 })
400
401 expect(subPlaylist).to.contain(segmentName)
402
403 const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls'
404 await checkLiveSegmentHash({
405 server,
406 baseUrlSegment: baseUrlAndPath,
407 videoUUID: video.uuid,
408 segmentName,
409 hlsPlaylist
410 })
411 }
412 }
413 }
414
415 function updateConf (resolutions: number[]) { 376 function updateConf (resolutions: number[]) {
416 return servers[0].config.updateCustomSubConfig({ 377 return servers[0].config.updateCustomSubConfig({
417 newConfig: { 378 newConfig: {
@@ -449,7 +410,14 @@ describe('Test live', function () {
449 await waitUntilLivePublishedOnAllServers(servers, liveVideoId) 410 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
450 await waitJobs(servers) 411 await waitJobs(servers)
451 412
452 await testVideoResolutions(liveVideoId, [ 720 ]) 413 await testVideoResolutions({
414 originServer: servers[0],
415 servers,
416 liveVideoId,
417 resolutions: [ 720 ],
418 objectStorage: false,
419 transcoded: true
420 })
453 421
454 await stopFfmpeg(ffmpegCommand) 422 await stopFfmpeg(ffmpegCommand)
455 }) 423 })
@@ -477,7 +445,14 @@ describe('Test live', function () {
477 await waitUntilLivePublishedOnAllServers(servers, liveVideoId) 445 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
478 await waitJobs(servers) 446 await waitJobs(servers)
479 447
480 await testVideoResolutions(liveVideoId, resolutions.concat([ 720 ])) 448 await testVideoResolutions({
449 originServer: servers[0],
450 servers,
451 liveVideoId,
452 resolutions: resolutions.concat([ 720 ]),
453 objectStorage: false,
454 transcoded: true
455 })
481 456
482 await stopFfmpeg(ffmpegCommand) 457 await stopFfmpeg(ffmpegCommand)
483 }) 458 })
@@ -522,7 +497,14 @@ describe('Test live', function () {
522 await waitUntilLivePublishedOnAllServers(servers, liveVideoId) 497 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
523 await waitJobs(servers) 498 await waitJobs(servers)
524 499
525 await testVideoResolutions(liveVideoId, resolutions) 500 await testVideoResolutions({
501 originServer: servers[0],
502 servers,
503 liveVideoId,
504 resolutions,
505 objectStorage: false,
506 transcoded: true
507 })
526 508
527 await stopFfmpeg(ffmpegCommand) 509 await stopFfmpeg(ffmpegCommand)
528 await commands[0].waitUntilEnded({ videoId: liveVideoId }) 510 await commands[0].waitUntilEnded({ videoId: liveVideoId })
@@ -538,7 +520,7 @@ describe('Test live', function () {
538 } 520 }
539 521
540 const minBitrateLimits = { 522 const minBitrateLimits = {
541 720: 5500 * 1000, 523 720: 4800 * 1000,
542 360: 1000 * 1000, 524 360: 1000 * 1000,
543 240: 550 * 1000 525 240: 550 * 1000
544 } 526 }
@@ -551,8 +533,8 @@ describe('Test live', function () {
551 expect(video.files).to.have.lengthOf(0) 533 expect(video.files).to.have.lengthOf(0)
552 534
553 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS) 535 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
554 await makeRawRequest(hlsPlaylist.playlistUrl, HttpStatusCode.OK_200) 536 await makeRawRequest({ url: hlsPlaylist.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
555 await makeRawRequest(hlsPlaylist.segmentsSha256Url, HttpStatusCode.OK_200) 537 await makeRawRequest({ url: hlsPlaylist.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
556 538
557 // We should have generated random filenames 539 // We should have generated random filenames
558 expect(basename(hlsPlaylist.playlistUrl)).to.not.equal('master.m3u8') 540 expect(basename(hlsPlaylist.playlistUrl)).to.not.equal('master.m3u8')
@@ -569,7 +551,7 @@ describe('Test live', function () {
569 if (resolution >= 720) { 551 if (resolution >= 720) {
570 expect(file.fps).to.be.approximately(60, 10) 552 expect(file.fps).to.be.approximately(60, 10)
571 } else { 553 } else {
572 expect(file.fps).to.be.approximately(30, 2) 554 expect(file.fps).to.be.approximately(30, 3)
573 } 555 }
574 556
575 const filename = basename(file.fileUrl) 557 const filename = basename(file.fileUrl)
@@ -583,8 +565,8 @@ describe('Test live', function () {
583 expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height]) 565 expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height])
584 expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height]) 566 expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height])
585 567
586 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200) 568 await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
587 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 569 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
588 } 570 }
589 } 571 }
590 }) 572 })
@@ -611,7 +593,14 @@ describe('Test live', function () {
611 await waitUntilLivePublishedOnAllServers(servers, liveVideoId) 593 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
612 await waitJobs(servers) 594 await waitJobs(servers)
613 595
614 await testVideoResolutions(liveVideoId, resolutions) 596 await testVideoResolutions({
597 originServer: servers[0],
598 servers,
599 liveVideoId,
600 resolutions,
601 objectStorage: false,
602 transcoded: true
603 })
615 604
616 await stopFfmpeg(ffmpegCommand) 605 await stopFfmpeg(ffmpegCommand)
617 await commands[0].waitUntilEnded({ videoId: liveVideoId }) 606 await commands[0].waitUntilEnded({ videoId: liveVideoId })
@@ -640,7 +629,14 @@ describe('Test live', function () {
640 await waitUntilLivePublishedOnAllServers(servers, liveVideoId) 629 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
641 await waitJobs(servers) 630 await waitJobs(servers)
642 631
643 await testVideoResolutions(liveVideoId, [ 720 ]) 632 await testVideoResolutions({
633 originServer: servers[0],
634 servers,
635 liveVideoId,
636 resolutions: [ 720 ],
637 objectStorage: false,
638 transcoded: true
639 })
644 640
645 await stopFfmpeg(ffmpegCommand) 641 await stopFfmpeg(ffmpegCommand)
646 await commands[0].waitUntilEnded({ videoId: liveVideoId }) 642 await commands[0].waitUntilEnded({ videoId: liveVideoId })
@@ -700,9 +696,15 @@ describe('Test live', function () {
700 commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) 696 commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
701 ]) 697 ])
702 698
703 await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoId, playlistNumber: 0, segment: 2 }) 699 for (const videoUUID of [ liveVideoId, liveVideoReplayId, permanentLiveVideoReplayId ]) {
704 await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoReplayId, playlistNumber: 0, segment: 2 }) 700 await commands[0].waitUntilSegmentGeneration({
705 await commands[0].waitUntilSegmentGeneration({ videoUUID: permanentLiveVideoReplayId, playlistNumber: 0, segment: 2 }) 701 server: servers[0],
702 videoUUID,
703 playlistNumber: 0,
704 segment: 2,
705 objectStorage: false
706 })
707 }
706 708
707 { 709 {
708 const video = await servers[0].videos.get({ id: permanentLiveVideoReplayId }) 710 const video = await servers[0].videos.get({ id: permanentLiveVideoReplayId })
diff --git a/server/tests/api/notifications/admin-notifications.ts b/server/tests/api/notifications/admin-notifications.ts
index b3bb4888e..07c981a37 100644
--- a/server/tests/api/notifications/admin-notifications.ts
+++ b/server/tests/api/notifications/admin-notifications.ts
@@ -37,7 +37,7 @@ describe('Test admin notifications', function () {
37 plugins: { 37 plugins: {
38 index: { 38 index: {
39 enabled: true, 39 enabled: true,
40 check_latest_versions_interval: '5 seconds' 40 check_latest_versions_interval: '3 seconds'
41 } 41 }
42 } 42 }
43 } 43 }
@@ -62,7 +62,7 @@ describe('Test admin notifications', function () {
62 62
63 describe('Latest PeerTube version notification', function () { 63 describe('Latest PeerTube version notification', function () {
64 64
65 it('Should not send a notification to admins if there is not a new version', async function () { 65 it('Should not send a notification to admins if there is no new version', async function () {
66 this.timeout(30000) 66 this.timeout(30000)
67 67
68 joinPeerTubeServer.setLatestVersion('1.4.2') 68 joinPeerTubeServer.setLatestVersion('1.4.2')
@@ -71,7 +71,7 @@ describe('Test admin notifications', function () {
71 await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' }) 71 await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' })
72 }) 72 })
73 73
74 it('Should send a notification to admins on new plugin version', async function () { 74 it('Should send a notification to admins on new version', async function () {
75 this.timeout(30000) 75 this.timeout(30000)
76 76
77 joinPeerTubeServer.setLatestVersion('15.4.2') 77 joinPeerTubeServer.setLatestVersion('15.4.2')
diff --git a/server/tests/api/notifications/moderation-notifications.ts b/server/tests/api/notifications/moderation-notifications.ts
index d8a7d576e..5a632fb22 100644
--- a/server/tests/api/notifications/moderation-notifications.ts
+++ b/server/tests/api/notifications/moderation-notifications.ts
@@ -382,7 +382,7 @@ describe('Test moderation notifications', function () {
382 }) 382 })
383 383
384 it('Should send a notification only to admin when there is a new instance follower', async function () { 384 it('Should send a notification only to admin when there is a new instance follower', async function () {
385 this.timeout(20000) 385 this.timeout(60000)
386 386
387 await servers[2].follows.follow({ hosts: [ servers[0].url ] }) 387 await servers[2].follows.follow({ hosts: [ servers[0].url ] })
388 388
@@ -545,7 +545,7 @@ describe('Test moderation notifications', function () {
545 }) 545 })
546 546
547 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { 547 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () {
548 this.timeout(40000) 548 this.timeout(120000)
549 549
550 const updateAt = new Date(new Date().getTime() + 1000000) 550 const updateAt = new Date(new Date().getTime() + 1000000)
551 551
diff --git a/server/tests/api/object-storage/index.ts b/server/tests/api/object-storage/index.ts
index f319d6ef5..1f4489fa3 100644
--- a/server/tests/api/object-storage/index.ts
+++ b/server/tests/api/object-storage/index.ts
@@ -1,3 +1,4 @@
1export * from './live' 1export * from './live'
2export * from './video-imports' 2export * from './video-imports'
3export * from './video-static-file-privacy'
3export * from './videos' 4export * from './videos'
diff --git a/server/tests/api/object-storage/live.ts b/server/tests/api/object-storage/live.ts
index 0958ffe0f..ad2b554b7 100644
--- a/server/tests/api/object-storage/live.ts
+++ b/server/tests/api/object-storage/live.ts
@@ -1,9 +1,9 @@
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 { expectStartWith } from '@server/tests/shared' 4import { expectStartWith, testVideoResolutions } from '@server/tests/shared'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils' 5import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode, LiveVideoCreate, VideoFile, VideoPrivacy } from '@shared/models' 6import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@shared/models'
7import { 7import {
8 createMultipleServers, 8 createMultipleServers,
9 doubleFollow, 9 doubleFollow,
@@ -35,54 +35,56 @@ async function createLive (server: PeerTubeServer, permanent: boolean) {
35 return uuid 35 return uuid
36} 36}
37 37
38async function checkFiles (files: VideoFile[]) { 38async function checkFilesExist (servers: PeerTubeServer[], videoUUID: string, numberOfFiles: number) {
39 for (const file of files) { 39 for (const server of servers) {
40 expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl()) 40 const video = await server.videos.get({ id: videoUUID })
41 41
42 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 42 expect(video.files).to.have.lengthOf(0)
43 } 43 expect(video.streamingPlaylists).to.have.lengthOf(1)
44}
45 44
46async function getFiles (server: PeerTubeServer, videoUUID: string) { 45 const files = video.streamingPlaylists[0].files
47 const video = await server.videos.get({ id: videoUUID }) 46 expect(files).to.have.lengthOf(numberOfFiles)
48 47
49 expect(video.files).to.have.lengthOf(0) 48 for (const file of files) {
50 expect(video.streamingPlaylists).to.have.lengthOf(1) 49 expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
51 50
52 return video.streamingPlaylists[0].files 51 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
52 }
53 }
53} 54}
54 55
55async function streamAndEnd (servers: PeerTubeServer[], liveUUID: string) { 56async function checkFilesCleanup (server: PeerTubeServer, videoUUID: string, resolutions: number[]) {
56 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveUUID }) 57 const resolutionFiles = resolutions.map((_value, i) => `${i}.m3u8`)
57 await waitUntilLivePublishedOnAllServers(servers, liveUUID)
58
59 const videoLiveDetails = await servers[0].videos.get({ id: liveUUID })
60 const liveDetails = await servers[0].live.get({ videoId: liveUUID })
61 58
62 await stopFfmpeg(ffmpegCommand) 59 for (const playlistName of [ 'master.m3u8' ].concat(resolutionFiles)) {
63 60 await server.live.getPlaylistFile({
64 if (liveDetails.permanentLive) { 61 videoUUID,
65 await waitUntilLiveWaitingOnAllServers(servers, liveUUID) 62 playlistName,
66 } else { 63 expectedStatus: HttpStatusCode.NOT_FOUND_404,
67 await waitUntilLiveReplacedByReplayOnAllServers(servers, liveUUID) 64 objectStorage: true
65 })
68 } 66 }
69 67
70 await waitJobs(servers) 68 await server.live.getSegmentFile({
71 69 videoUUID,
72 return { videoLiveDetails, liveDetails } 70 playlistNumber: 0,
71 segment: 0,
72 objectStorage: true,
73 expectedStatus: HttpStatusCode.NOT_FOUND_404
74 })
73} 75}
74 76
75describe('Object storage for lives', function () { 77describe('Object storage for lives', function () {
76 if (areObjectStorageTestsDisabled()) return 78 if (areMockObjectStorageTestsDisabled()) return
77 79
78 let servers: PeerTubeServer[] 80 let servers: PeerTubeServer[]
79 81
80 before(async function () { 82 before(async function () {
81 this.timeout(120000) 83 this.timeout(120000)
82 84
83 await ObjectStorageCommand.prepareDefaultBuckets() 85 await ObjectStorageCommand.prepareDefaultMockBuckets()
84 86
85 servers = await createMultipleServers(2, ObjectStorageCommand.getDefaultConfig()) 87 servers = await createMultipleServers(2, ObjectStorageCommand.getDefaultMockConfig())
86 88
87 await setAccessTokensToServers(servers) 89 await setAccessTokensToServers(servers)
88 await setDefaultVideoChannel(servers) 90 await setDefaultVideoChannel(servers)
@@ -100,57 +102,124 @@ describe('Object storage for lives', function () {
100 videoUUID = await createLive(servers[0], false) 102 videoUUID = await createLive(servers[0], false)
101 }) 103 })
102 104
103 it('Should create a live and save the replay on object storage', async function () { 105 it('Should create a live and publish it on object storage', async function () {
106 this.timeout(220000)
107
108 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
109 await waitUntilLivePublishedOnAllServers(servers, videoUUID)
110
111 await testVideoResolutions({
112 originServer: servers[0],
113 servers,
114 liveVideoId: videoUUID,
115 resolutions: [ 720 ],
116 transcoded: false,
117 objectStorage: true
118 })
119
120 await stopFfmpeg(ffmpegCommand)
121 })
122
123 it('Should have saved the replay on object storage', async function () {
104 this.timeout(220000) 124 this.timeout(220000)
105 125
106 await streamAndEnd(servers, videoUUID) 126 await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUID)
127 await waitJobs(servers)
107 128
108 for (const server of servers) { 129 await checkFilesExist(servers, videoUUID, 1)
109 const files = await getFiles(server, videoUUID) 130 })
110 expect(files).to.have.lengthOf(1)
111 131
112 await checkFiles(files) 132 it('Should have cleaned up live files from object storage', async function () {
113 } 133 await checkFilesCleanup(servers[0], videoUUID, [ 720 ])
114 }) 134 })
115 }) 135 })
116 136
117 describe('With live transcoding', async function () { 137 describe('With live transcoding', async function () {
118 let videoUUIDPermanent: string 138 const resolutions = [ 720, 480, 360, 240, 144 ]
119 let videoUUIDNonPermanent: string
120 139
121 before(async function () { 140 before(async function () {
122 await servers[0].config.enableLive({ transcoding: true }) 141 await servers[0].config.enableLive({ transcoding: true })
123
124 videoUUIDPermanent = await createLive(servers[0], true)
125 videoUUIDNonPermanent = await createLive(servers[0], false)
126 }) 142 })
127 143
128 it('Should create a live and save the replay on object storage', async function () { 144 describe('Normal replay', function () {
129 this.timeout(240000) 145 let videoUUIDNonPermanent: string
146
147 before(async function () {
148 videoUUIDNonPermanent = await createLive(servers[0], false)
149 })
150
151 it('Should create a live and publish it on object storage', async function () {
152 this.timeout(240000)
153
154 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDNonPermanent })
155 await waitUntilLivePublishedOnAllServers(servers, videoUUIDNonPermanent)
156
157 await testVideoResolutions({
158 originServer: servers[0],
159 servers,
160 liveVideoId: videoUUIDNonPermanent,
161 resolutions,
162 transcoded: true,
163 objectStorage: true
164 })
165
166 await stopFfmpeg(ffmpegCommand)
167 })
130 168
131 await streamAndEnd(servers, videoUUIDNonPermanent) 169 it('Should have saved the replay on object storage', async function () {
170 this.timeout(220000)
132 171
133 for (const server of servers) { 172 await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUIDNonPermanent)
134 const files = await getFiles(server, videoUUIDNonPermanent) 173 await waitJobs(servers)
135 expect(files).to.have.lengthOf(5)
136 174
137 await checkFiles(files) 175 await checkFilesExist(servers, videoUUIDNonPermanent, 5)
138 } 176 })
177
178 it('Should have cleaned up live files from object storage', async function () {
179 await checkFilesCleanup(servers[0], videoUUIDNonPermanent, resolutions)
180 })
139 }) 181 })
140 182
141 it('Should create a live and save the replay of permanent live on object storage', async function () { 183 describe('Permanent replay', function () {
142 this.timeout(240000) 184 let videoUUIDPermanent: string
185
186 before(async function () {
187 videoUUIDPermanent = await createLive(servers[0], true)
188 })
189
190 it('Should create a live and publish it on object storage', async function () {
191 this.timeout(240000)
192
193 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDPermanent })
194 await waitUntilLivePublishedOnAllServers(servers, videoUUIDPermanent)
195
196 await testVideoResolutions({
197 originServer: servers[0],
198 servers,
199 liveVideoId: videoUUIDPermanent,
200 resolutions,
201 transcoded: true,
202 objectStorage: true
203 })
204
205 await stopFfmpeg(ffmpegCommand)
206 })
207
208 it('Should have saved the replay on object storage', async function () {
209 this.timeout(220000)
143 210
144 const { videoLiveDetails } = await streamAndEnd(servers, videoUUIDPermanent) 211 await waitUntilLiveWaitingOnAllServers(servers, videoUUIDPermanent)
212 await waitJobs(servers)
145 213
146 const replay = await findExternalSavedVideo(servers[0], videoLiveDetails) 214 const videoLiveDetails = await servers[0].videos.get({ id: videoUUIDPermanent })
215 const replay = await findExternalSavedVideo(servers[0], videoLiveDetails)
147 216
148 for (const server of servers) { 217 await checkFilesExist(servers, replay.uuid, 5)
149 const files = await getFiles(server, replay.uuid) 218 })
150 expect(files).to.have.lengthOf(5)
151 219
152 await checkFiles(files) 220 it('Should have cleaned up live files from object storage', async function () {
153 } 221 await checkFilesCleanup(servers[0], videoUUIDPermanent, resolutions)
222 })
154 }) 223 })
155 }) 224 })
156 225
diff --git a/server/tests/api/object-storage/video-imports.ts b/server/tests/api/object-storage/video-imports.ts
index f688c7018..11c866411 100644
--- a/server/tests/api/object-storage/video-imports.ts
+++ b/server/tests/api/object-storage/video-imports.ts
@@ -2,7 +2,7 @@
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { expectStartWith, FIXTURE_URLS } from '@server/tests/shared' 4import { expectStartWith, FIXTURE_URLS } from '@server/tests/shared'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils' 5import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode, VideoPrivacy } from '@shared/models' 6import { HttpStatusCode, VideoPrivacy } from '@shared/models'
7import { 7import {
8 createSingleServer, 8 createSingleServer,
@@ -29,16 +29,16 @@ async function importVideo (server: PeerTubeServer) {
29} 29}
30 30
31describe('Object storage for video import', function () { 31describe('Object storage for video import', function () {
32 if (areObjectStorageTestsDisabled()) return 32 if (areMockObjectStorageTestsDisabled()) return
33 33
34 let server: PeerTubeServer 34 let server: PeerTubeServer
35 35
36 before(async function () { 36 before(async function () {
37 this.timeout(120000) 37 this.timeout(120000)
38 38
39 await ObjectStorageCommand.prepareDefaultBuckets() 39 await ObjectStorageCommand.prepareDefaultMockBuckets()
40 40
41 server = await createSingleServer(1, ObjectStorageCommand.getDefaultConfig()) 41 server = await createSingleServer(1, ObjectStorageCommand.getDefaultMockConfig())
42 42
43 await setAccessTokensToServers([ server ]) 43 await setAccessTokensToServers([ server ])
44 await setDefaultVideoChannel([ server ]) 44 await setDefaultVideoChannel([ server ])
@@ -64,9 +64,9 @@ describe('Object storage for video import', function () {
64 expect(video.streamingPlaylists).to.have.lengthOf(0) 64 expect(video.streamingPlaylists).to.have.lengthOf(0)
65 65
66 const fileUrl = video.files[0].fileUrl 66 const fileUrl = video.files[0].fileUrl
67 expectStartWith(fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 67 expectStartWith(fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
68 68
69 await makeRawRequest(fileUrl, HttpStatusCode.OK_200) 69 await makeRawRequest({ url: fileUrl, expectedStatus: HttpStatusCode.OK_200 })
70 }) 70 })
71 }) 71 })
72 72
@@ -89,15 +89,15 @@ describe('Object storage for video import', function () {
89 expect(video.streamingPlaylists[0].files).to.have.lengthOf(5) 89 expect(video.streamingPlaylists[0].files).to.have.lengthOf(5)
90 90
91 for (const file of video.files) { 91 for (const file of video.files) {
92 expectStartWith(file.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 92 expectStartWith(file.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
93 93
94 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 94 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
95 } 95 }
96 96
97 for (const file of video.streamingPlaylists[0].files) { 97 for (const file of video.streamingPlaylists[0].files) {
98 expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl()) 98 expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
99 99
100 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 100 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
101 } 101 }
102 }) 102 })
103 }) 103 })
diff --git a/server/tests/api/object-storage/video-static-file-privacy.ts b/server/tests/api/object-storage/video-static-file-privacy.ts
new file mode 100644
index 000000000..62edd10ba
--- /dev/null
+++ b/server/tests/api/object-storage/video-static-file-privacy.ts
@@ -0,0 +1,402 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { basename } from 'path'
5import { expectStartWith } from '@server/tests/shared'
6import { areScalewayObjectStorageTestsDisabled, getAllFiles, getHLS } from '@shared/core-utils'
7import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models'
8import {
9 cleanupTests,
10 createSingleServer,
11 findExternalSavedVideo,
12 makeRawRequest,
13 ObjectStorageCommand,
14 PeerTubeServer,
15 sendRTMPStream,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 stopFfmpeg,
19 waitJobs
20} from '@shared/server-commands'
21
22function extractFilenameFromUrl (url: string) {
23 const parts = basename(url).split(':')
24
25 return parts[parts.length - 1]
26}
27
28describe('Object storage for video static file privacy', function () {
29 // We need real world object storage to check ACL
30 if (areScalewayObjectStorageTestsDisabled()) return
31
32 let server: PeerTubeServer
33 let userToken: string
34
35 // ---------------------------------------------------------------------------
36
37 async function checkPrivateVODFiles (uuid: string) {
38 const video = await server.videos.getWithToken({ id: uuid })
39
40 for (const file of video.files) {
41 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/')
42
43 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
44 }
45
46 for (const file of getAllFiles(video)) {
47 const internalFileUrl = await server.sql.getInternalFileUrl(file.id)
48 expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl())
49 await makeRawRequest({ url: internalFileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
50 }
51
52 const hls = getHLS(video)
53
54 if (hls) {
55 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
56 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
57 }
58
59 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
60 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
61
62 for (const file of hls.files) {
63 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
64
65 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
66 }
67 }
68 }
69
70 async function checkPublicVODFiles (uuid: string) {
71 const video = await server.videos.getWithToken({ id: uuid })
72
73 for (const file of getAllFiles(video)) {
74 expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl())
75
76 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
77 }
78
79 const hls = getHLS(video)
80
81 if (hls) {
82 expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl())
83 expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
84
85 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
86 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
87 }
88 }
89
90 // ---------------------------------------------------------------------------
91
92 before(async function () {
93 this.timeout(120000)
94
95 server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig({ serverNumber: 1 }))
96 await setAccessTokensToServers([ server ])
97 await setDefaultVideoChannel([ server ])
98
99 await server.config.enableMinimumTranscoding()
100
101 userToken = await server.users.generateUserAndToken('user1')
102 })
103
104 describe('VOD', function () {
105 let privateVideoUUID: string
106 let publicVideoUUID: string
107 let userPrivateVideoUUID: string
108
109 // ---------------------------------------------------------------------------
110
111 async function getSampleFileUrls (videoId: string) {
112 const video = await server.videos.getWithToken({ id: videoId })
113
114 return {
115 webTorrentFile: video.files[0].fileUrl,
116 hlsFile: getHLS(video).files[0].fileUrl
117 }
118 }
119
120 // ---------------------------------------------------------------------------
121
122 it('Should upload a private video and have appropriate object storage ACL', async function () {
123 this.timeout(60000)
124
125 {
126 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
127 privateVideoUUID = uuid
128 }
129
130 {
131 const { uuid } = await server.videos.quickUpload({ name: 'user video', token: userToken, privacy: VideoPrivacy.PRIVATE })
132 userPrivateVideoUUID = uuid
133 }
134
135 await waitJobs([ server ])
136
137 await checkPrivateVODFiles(privateVideoUUID)
138 })
139
140 it('Should upload a public video and have appropriate object storage ACL', async function () {
141 this.timeout(60000)
142
143 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED })
144 await waitJobs([ server ])
145
146 publicVideoUUID = uuid
147
148 await checkPublicVODFiles(publicVideoUUID)
149 })
150
151 it('Should not get files without appropriate OAuth token', async function () {
152 this.timeout(60000)
153
154 const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
155
156 await makeRawRequest({ url: webTorrentFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
157 await makeRawRequest({ url: webTorrentFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
158
159 await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
160 await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
161 })
162
163 it('Should not get HLS file of another video', async function () {
164 this.timeout(60000)
165
166 const privateVideo = await server.videos.getWithToken({ id: privateVideoUUID })
167 const hlsFilename = basename(getHLS(privateVideo).files[0].fileUrl)
168
169 const badUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + userPrivateVideoUUID + '/' + hlsFilename
170 const goodUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + privateVideoUUID + '/' + hlsFilename
171
172 await makeRawRequest({ url: badUrl, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
173 await makeRawRequest({ url: goodUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
174 })
175
176 it('Should correctly check OAuth or video file token', async function () {
177 this.timeout(60000)
178
179 const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID })
180 const goodVideoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
181
182 const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
183
184 for (const url of [ webTorrentFile, hlsFile ]) {
185 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
186 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
187 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
188
189 await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
190 await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
191 }
192 })
193
194 it('Should update public video to private', async function () {
195 this.timeout(60000)
196
197 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } })
198
199 await checkPrivateVODFiles(publicVideoUUID)
200 })
201
202 it('Should update private video to public', async function () {
203 this.timeout(60000)
204
205 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
206
207 await checkPublicVODFiles(publicVideoUUID)
208 })
209 })
210
211 describe('Live', function () {
212 let normalLiveId: string
213 let normalLive: LiveVideo
214
215 let permanentLiveId: string
216 let permanentLive: LiveVideo
217
218 let unrelatedFileToken: string
219
220 // ---------------------------------------------------------------------------
221
222 async function checkLiveFiles (live: LiveVideo, liveId: string) {
223 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
224 await server.live.waitUntilPublished({ videoId: liveId })
225
226 const video = await server.videos.getWithToken({ id: liveId })
227 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
228
229 const hls = video.streamingPlaylists[0]
230
231 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
232 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
233
234 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
235 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
236
237 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
238 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
239
240 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
241 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
242 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
243 }
244
245 await stopFfmpeg(ffmpegCommand)
246 }
247
248 async function checkReplay (replay: VideoDetails) {
249 const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
250
251 const hls = replay.streamingPlaylists[0]
252 expect(hls.files).to.not.have.lengthOf(0)
253
254 for (const file of hls.files) {
255 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
256 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
257
258 await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
259 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
260 await makeRawRequest({
261 url: file.fileUrl,
262 query: { videoFileToken: unrelatedFileToken },
263 expectedStatus: HttpStatusCode.FORBIDDEN_403
264 })
265 }
266
267 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
268 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
269
270 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
271 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
272
273 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
274 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
275 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
276 }
277 }
278
279 // ---------------------------------------------------------------------------
280
281 before(async function () {
282 await server.config.enableMinimumTranscoding()
283
284 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
285 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
286
287 await server.config.enableLive({
288 allowReplay: true,
289 transcoding: true,
290 resolutions: 'min'
291 })
292
293 {
294 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: false, privacy: VideoPrivacy.PRIVATE })
295 normalLiveId = video.uuid
296 normalLive = live
297 }
298
299 {
300 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: true, privacy: VideoPrivacy.PRIVATE })
301 permanentLiveId = video.uuid
302 permanentLive = live
303 }
304 })
305
306 it('Should create a private normal live and have a private static path', async function () {
307 this.timeout(240000)
308
309 await checkLiveFiles(normalLive, normalLiveId)
310 })
311
312 it('Should create a private permanent live and have a private static path', async function () {
313 this.timeout(240000)
314
315 await checkLiveFiles(permanentLive, permanentLiveId)
316 })
317
318 it('Should have created a replay of the normal live with a private static path', async function () {
319 this.timeout(240000)
320
321 await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
322
323 const replay = await server.videos.getWithToken({ id: normalLiveId })
324 await checkReplay(replay)
325 })
326
327 it('Should have created a replay of the permanent live with a private static path', async function () {
328 this.timeout(240000)
329
330 await server.live.waitUntilWaiting({ videoId: permanentLiveId })
331 await waitJobs([ server ])
332
333 const live = await server.videos.getWithToken({ id: permanentLiveId })
334 const replayFromList = await findExternalSavedVideo(server, live)
335 const replay = await server.videos.getWithToken({ id: replayFromList.id })
336
337 await checkReplay(replay)
338 })
339 })
340
341 describe('With private files proxy disabled and public ACL for private files', function () {
342 let videoUUID: string
343
344 before(async function () {
345 this.timeout(240000)
346
347 await server.kill()
348
349 const config = ObjectStorageCommand.getDefaultScalewayConfig({
350 serverNumber: 1,
351 enablePrivateProxy: false,
352 privateACL: 'public-read'
353 })
354 await server.run(config)
355
356 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
357 videoUUID = uuid
358
359 await waitJobs([ server ])
360 })
361
362 it('Should display object storage path for a private video and be able to access them', async function () {
363 this.timeout(60000)
364
365 await checkPublicVODFiles(videoUUID)
366 })
367
368 it('Should not be able to access object storage proxy', async function () {
369 const privateVideo = await server.videos.getWithToken({ id: videoUUID })
370 const webtorrentFilename = extractFilenameFromUrl(privateVideo.files[0].fileUrl)
371 const hlsFilename = extractFilenameFromUrl(getHLS(privateVideo).files[0].fileUrl)
372
373 await makeRawRequest({
374 url: server.url + '/object-storage-proxy/webseed/private/' + webtorrentFilename,
375 token: server.accessToken,
376 expectedStatus: HttpStatusCode.BAD_REQUEST_400
377 })
378
379 await makeRawRequest({
380 url: server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + videoUUID + '/' + hlsFilename,
381 token: server.accessToken,
382 expectedStatus: HttpStatusCode.BAD_REQUEST_400
383 })
384 })
385 })
386
387 after(async function () {
388 this.timeout(240000)
389
390 const { data } = await server.videos.listAllForAdmin()
391
392 for (const v of data) {
393 await server.videos.remove({ id: v.uuid })
394 }
395
396 for (const v of data) {
397 await server.servers.waitUntilLog('Removed files of video ' + v.url)
398 }
399
400 await cleanupTests([ server ])
401 })
402})
diff --git a/server/tests/api/object-storage/videos.ts b/server/tests/api/object-storage/videos.ts
index 3e65e1093..d1875febb 100644
--- a/server/tests/api/object-storage/videos.ts
+++ b/server/tests/api/object-storage/videos.ts
@@ -11,7 +11,7 @@ import {
11 generateHighBitrateVideo, 11 generateHighBitrateVideo,
12 MockObjectStorage 12 MockObjectStorage
13} from '@server/tests/shared' 13} from '@server/tests/shared'
14import { areObjectStorageTestsDisabled } from '@shared/core-utils' 14import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
15import { HttpStatusCode, VideoDetails } from '@shared/models' 15import { HttpStatusCode, VideoDetails } from '@shared/models'
16import { 16import {
17 cleanupTests, 17 cleanupTests,
@@ -52,18 +52,18 @@ async function checkFiles (options: {
52 for (const file of video.files) { 52 for (const file of video.files) {
53 const baseUrl = baseMockUrl 53 const baseUrl = baseMockUrl
54 ? `${baseMockUrl}/${webtorrentBucket}/` 54 ? `${baseMockUrl}/${webtorrentBucket}/`
55 : `http://${webtorrentBucket}.${ObjectStorageCommand.getEndpointHost()}/` 55 : `http://${webtorrentBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
56 56
57 const prefix = webtorrentPrefix || '' 57 const prefix = webtorrentPrefix || ''
58 const start = baseUrl + prefix 58 const start = baseUrl + prefix
59 59
60 expectStartWith(file.fileUrl, start) 60 expectStartWith(file.fileUrl, start)
61 61
62 const res = await makeRawRequest(file.fileDownloadUrl, HttpStatusCode.FOUND_302) 62 const res = await makeRawRequest({ url: file.fileDownloadUrl, expectedStatus: HttpStatusCode.FOUND_302 })
63 const location = res.headers['location'] 63 const location = res.headers['location']
64 expectStartWith(location, start) 64 expectStartWith(location, start)
65 65
66 await makeRawRequest(location, HttpStatusCode.OK_200) 66 await makeRawRequest({ url: location, expectedStatus: HttpStatusCode.OK_200 })
67 } 67 }
68 68
69 const hls = video.streamingPlaylists[0] 69 const hls = video.streamingPlaylists[0]
@@ -73,7 +73,7 @@ async function checkFiles (options: {
73 73
74 const baseUrl = baseMockUrl 74 const baseUrl = baseMockUrl
75 ? `${baseMockUrl}/${playlistBucket}/` 75 ? `${baseMockUrl}/${playlistBucket}/`
76 : `http://${playlistBucket}.${ObjectStorageCommand.getEndpointHost()}/` 76 : `http://${playlistBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
77 77
78 const prefix = playlistPrefix || '' 78 const prefix = playlistPrefix || ''
79 const start = baseUrl + prefix 79 const start = baseUrl + prefix
@@ -81,19 +81,19 @@ async function checkFiles (options: {
81 expectStartWith(hls.playlistUrl, start) 81 expectStartWith(hls.playlistUrl, start)
82 expectStartWith(hls.segmentsSha256Url, start) 82 expectStartWith(hls.segmentsSha256Url, start)
83 83
84 await makeRawRequest(hls.playlistUrl, HttpStatusCode.OK_200) 84 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
85 85
86 const resSha = await makeRawRequest(hls.segmentsSha256Url, HttpStatusCode.OK_200) 86 const resSha = await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
87 expect(JSON.stringify(resSha.body)).to.not.throw 87 expect(JSON.stringify(resSha.body)).to.not.throw
88 88
89 for (const file of hls.files) { 89 for (const file of hls.files) {
90 expectStartWith(file.fileUrl, start) 90 expectStartWith(file.fileUrl, start)
91 91
92 const res = await makeRawRequest(file.fileDownloadUrl, HttpStatusCode.FOUND_302) 92 const res = await makeRawRequest({ url: file.fileDownloadUrl, expectedStatus: HttpStatusCode.FOUND_302 })
93 const location = res.headers['location'] 93 const location = res.headers['location']
94 expectStartWith(location, start) 94 expectStartWith(location, start)
95 95
96 await makeRawRequest(location, HttpStatusCode.OK_200) 96 await makeRawRequest({ url: location, expectedStatus: HttpStatusCode.OK_200 })
97 } 97 }
98 } 98 }
99 99
@@ -104,7 +104,7 @@ async function checkFiles (options: {
104 expect(torrent.files.length).to.equal(1) 104 expect(torrent.files.length).to.equal(1)
105 expect(torrent.files[0].path).to.exist.and.to.not.equal('') 105 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
106 106
107 const res = await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 107 const res = await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
108 expect(res.body).to.have.length.above(100) 108 expect(res.body).to.have.length.above(100)
109 } 109 }
110 110
@@ -141,16 +141,16 @@ function runTestSuite (options: {
141 const port = await mockObjectStorage.initialize() 141 const port = await mockObjectStorage.initialize()
142 baseMockUrl = options.useMockBaseUrl ? `http://localhost:${port}` : undefined 142 baseMockUrl = options.useMockBaseUrl ? `http://localhost:${port}` : undefined
143 143
144 await ObjectStorageCommand.createBucket(options.playlistBucket) 144 await ObjectStorageCommand.createMockBucket(options.playlistBucket)
145 await ObjectStorageCommand.createBucket(options.webtorrentBucket) 145 await ObjectStorageCommand.createMockBucket(options.webtorrentBucket)
146 146
147 const config = { 147 const config = {
148 object_storage: { 148 object_storage: {
149 enabled: true, 149 enabled: true,
150 endpoint: 'http://' + ObjectStorageCommand.getEndpointHost(), 150 endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
151 region: ObjectStorageCommand.getRegion(), 151 region: ObjectStorageCommand.getMockRegion(),
152 152
153 credentials: ObjectStorageCommand.getCredentialsConfig(), 153 credentials: ObjectStorageCommand.getMockCredentialsConfig(),
154 154
155 max_upload_part: options.maxUploadPart || '5MB', 155 max_upload_part: options.maxUploadPart || '5MB',
156 156
@@ -220,7 +220,7 @@ function runTestSuite (options: {
220 220
221 it('Should fetch correctly all the files', async function () { 221 it('Should fetch correctly all the files', async function () {
222 for (const url of deletedUrls.concat(keptUrls)) { 222 for (const url of deletedUrls.concat(keptUrls)) {
223 await makeRawRequest(url, HttpStatusCode.OK_200) 223 await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
224 } 224 }
225 }) 225 })
226 226
@@ -231,13 +231,13 @@ function runTestSuite (options: {
231 await waitJobs(servers) 231 await waitJobs(servers)
232 232
233 for (const url of deletedUrls) { 233 for (const url of deletedUrls) {
234 await makeRawRequest(url, HttpStatusCode.NOT_FOUND_404) 234 await makeRawRequest({ url, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
235 } 235 }
236 }) 236 })
237 237
238 it('Should have kept other files', async function () { 238 it('Should have kept other files', async function () {
239 for (const url of keptUrls) { 239 for (const url of keptUrls) {
240 await makeRawRequest(url, HttpStatusCode.OK_200) 240 await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
241 } 241 }
242 }) 242 })
243 243
@@ -261,7 +261,7 @@ function runTestSuite (options: {
261} 261}
262 262
263describe('Object storage for videos', function () { 263describe('Object storage for videos', function () {
264 if (areObjectStorageTestsDisabled()) return 264 if (areMockObjectStorageTestsDisabled()) return
265 265
266 describe('Test config', function () { 266 describe('Test config', function () {
267 let server: PeerTubeServer 267 let server: PeerTubeServer
@@ -269,17 +269,17 @@ describe('Object storage for videos', function () {
269 const baseConfig = { 269 const baseConfig = {
270 object_storage: { 270 object_storage: {
271 enabled: true, 271 enabled: true,
272 endpoint: 'http://' + ObjectStorageCommand.getEndpointHost(), 272 endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
273 region: ObjectStorageCommand.getRegion(), 273 region: ObjectStorageCommand.getMockRegion(),
274 274
275 credentials: ObjectStorageCommand.getCredentialsConfig(), 275 credentials: ObjectStorageCommand.getMockCredentialsConfig(),
276 276
277 streaming_playlists: { 277 streaming_playlists: {
278 bucket_name: ObjectStorageCommand.DEFAULT_PLAYLIST_BUCKET 278 bucket_name: ObjectStorageCommand.DEFAULT_PLAYLIST_MOCK_BUCKET
279 }, 279 },
280 280
281 videos: { 281 videos: {
282 bucket_name: ObjectStorageCommand.DEFAULT_WEBTORRENT_BUCKET 282 bucket_name: ObjectStorageCommand.DEFAULT_WEBTORRENT_MOCK_BUCKET
283 } 283 }
284 } 284 }
285 } 285 }
@@ -310,7 +310,7 @@ describe('Object storage for videos', function () {
310 it('Should fail with bad credentials', async function () { 310 it('Should fail with bad credentials', async function () {
311 this.timeout(60000) 311 this.timeout(60000)
312 312
313 await ObjectStorageCommand.prepareDefaultBuckets() 313 await ObjectStorageCommand.prepareDefaultMockBuckets()
314 314
315 const config = merge({}, baseConfig, { 315 const config = merge({}, baseConfig, {
316 object_storage: { 316 object_storage: {
@@ -323,7 +323,7 @@ describe('Object storage for videos', function () {
323 323
324 const { uuid } = await server.videos.quickUpload({ name: 'video' }) 324 const { uuid } = await server.videos.quickUpload({ name: 'video' })
325 325
326 await waitJobs([ server ], true) 326 await waitJobs([ server ], { skipDelayed: true })
327 const video = await server.videos.get({ id: uuid }) 327 const video = await server.videos.get({ id: uuid })
328 328
329 expectStartWith(video.files[0].fileUrl, server.url) 329 expectStartWith(video.files[0].fileUrl, server.url)
@@ -334,7 +334,7 @@ describe('Object storage for videos', function () {
334 it('Should succeed with credentials from env', async function () { 334 it('Should succeed with credentials from env', async function () {
335 this.timeout(60000) 335 this.timeout(60000)
336 336
337 await ObjectStorageCommand.prepareDefaultBuckets() 337 await ObjectStorageCommand.prepareDefaultMockBuckets()
338 338
339 const config = merge({}, baseConfig, { 339 const config = merge({}, baseConfig, {
340 object_storage: { 340 object_storage: {
@@ -345,7 +345,7 @@ describe('Object storage for videos', function () {
345 } 345 }
346 }) 346 })
347 347
348 const goodCredentials = ObjectStorageCommand.getCredentialsConfig() 348 const goodCredentials = ObjectStorageCommand.getMockCredentialsConfig()
349 349
350 server = await createSingleServer(1, config, { 350 server = await createSingleServer(1, config, {
351 env: { 351 env: {
@@ -358,10 +358,10 @@ describe('Object storage for videos', function () {
358 358
359 const { uuid } = await server.videos.quickUpload({ name: 'video' }) 359 const { uuid } = await server.videos.quickUpload({ name: 'video' })
360 360
361 await waitJobs([ server ], true) 361 await waitJobs([ server ], { skipDelayed: true })
362 const video = await server.videos.get({ id: uuid }) 362 const video = await server.videos.get({ id: uuid })
363 363
364 expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 364 expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
365 }) 365 })
366 366
367 after(async function () { 367 after(async function () {
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts
index 5abed358f..fb2e6e91c 100644
--- a/server/tests/api/redundancy/redundancy.ts
+++ b/server/tests/api/redundancy/redundancy.ts
@@ -5,7 +5,7 @@ import { readdir } from 'fs-extra'
5import magnetUtil from 'magnet-uri' 5import magnetUtil from 'magnet-uri'
6import { basename, join } from 'path' 6import { basename, join } from 'path'
7import { checkSegmentHash, checkVideoFilesWereRemoved, saveVideoInServers } from '@server/tests/shared' 7import { checkSegmentHash, checkVideoFilesWereRemoved, saveVideoInServers } from '@server/tests/shared'
8import { root, wait } from '@shared/core-utils' 8import { wait } from '@shared/core-utils'
9import { 9import {
10 HttpStatusCode, 10 HttpStatusCode,
11 VideoDetails, 11 VideoDetails,
@@ -39,7 +39,7 @@ async function checkMagnetWebseeds (file: VideoFile, baseWebseeds: string[], ser
39 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) 39 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
40 40
41 for (const url of parsed.urlList) { 41 for (const url of parsed.urlList) {
42 await makeRawRequest(url, HttpStatusCode.OK_200) 42 await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
43 } 43 }
44} 44}
45 45
@@ -125,7 +125,7 @@ async function check1WebSeed (videoUUID?: string) {
125 if (!videoUUID) videoUUID = video1Server2.uuid 125 if (!videoUUID) videoUUID = video1Server2.uuid
126 126
127 const webseeds = [ 127 const webseeds = [
128 `http://localhost:${servers[1].port}/static/webseed/` 128 `${servers[1].url}/static/webseed/`
129 ] 129 ]
130 130
131 for (const server of servers) { 131 for (const server of servers) {
@@ -144,8 +144,8 @@ async function check2Webseeds (videoUUID?: string) {
144 if (!videoUUID) videoUUID = video1Server2.uuid 144 if (!videoUUID) videoUUID = video1Server2.uuid
145 145
146 const webseeds = [ 146 const webseeds = [
147 `http://localhost:${servers[0].port}/static/redundancy/`, 147 `${servers[0].url}/static/redundancy/`,
148 `http://localhost:${servers[1].port}/static/webseed/` 148 `${servers[1].url}/static/webseed/`
149 ] 149 ]
150 150
151 for (const server of servers) { 151 for (const server of servers) {
@@ -159,12 +159,12 @@ async function check2Webseeds (videoUUID?: string) {
159 const { webtorrentFilenames } = await ensureSameFilenames(videoUUID) 159 const { webtorrentFilenames } = await ensureSameFilenames(videoUUID)
160 160
161 const directories = [ 161 const directories = [
162 'test' + servers[0].internalServerNumber + '/redundancy', 162 servers[0].getDirectoryPath('redundancy'),
163 'test' + servers[1].internalServerNumber + '/videos' 163 servers[1].getDirectoryPath('videos')
164 ] 164 ]
165 165
166 for (const directory of directories) { 166 for (const directory of directories) {
167 const files = await readdir(join(root(), directory)) 167 const files = await readdir(directory)
168 expect(files).to.have.length.at.least(4) 168 expect(files).to.have.length.at.least(4)
169 169
170 // Ensure we files exist on disk 170 // Ensure we files exist on disk
@@ -214,12 +214,12 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
214 const { hlsFilenames } = await ensureSameFilenames(videoUUID) 214 const { hlsFilenames } = await ensureSameFilenames(videoUUID)
215 215
216 const directories = [ 216 const directories = [
217 'test' + servers[0].internalServerNumber + '/redundancy/hls', 217 servers[0].getDirectoryPath('redundancy/hls'),
218 'test' + servers[1].internalServerNumber + '/streaming-playlists/hls' 218 servers[1].getDirectoryPath('streaming-playlists/hls')
219 ] 219 ]
220 220
221 for (const directory of directories) { 221 for (const directory of directories) {
222 const files = await readdir(join(root(), directory, videoUUID)) 222 const files = await readdir(join(directory, videoUUID))
223 expect(files).to.have.length.at.least(4) 223 expect(files).to.have.length.at.least(4)
224 224
225 // Ensure we files exist on disk 225 // Ensure we files exist on disk
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts
index 5998f58cc..e1ec2b069 100644
--- a/server/tests/api/server/follow-constraints.ts
+++ b/server/tests/api/server/follow-constraints.ts
@@ -1,8 +1,15 @@
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 { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5import { HttpStatusCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models' 4import { HttpStatusCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7describe('Test follow constraints', function () { 14describe('Test follow constraints', function () {
8 let servers: PeerTubeServer[] = [] 15 let servers: PeerTubeServer[] = []
@@ -189,6 +196,7 @@ describe('Test follow constraints', function () {
189 }) 196 })
190 197
191 describe('With a logged user', function () { 198 describe('With a logged user', function () {
199
192 it('Should get the local video', async function () { 200 it('Should get the local video', async function () {
193 await servers[0].videos.getWithToken({ token: userToken, id: video1UUID }) 201 await servers[0].videos.getWithToken({ token: userToken, id: video1UUID })
194 }) 202 })
@@ -229,6 +237,84 @@ describe('Test follow constraints', function () {
229 }) 237 })
230 }) 238 })
231 239
240 describe('When following a remote account', function () {
241
242 before(async function () {
243 this.timeout(60000)
244
245 await servers[0].follows.follow({ handles: [ 'root@' + servers[1].host ] })
246 await waitJobs(servers)
247 })
248
249 it('Should get the remote video with an unlogged user', async function () {
250 await servers[0].videos.get({ id: video2UUID })
251 })
252
253 it('Should get the remote video with a logged in user', async function () {
254 await servers[0].videos.getWithToken({ token: userToken, id: video2UUID })
255 })
256 })
257
258 describe('When unfollowing a remote account', function () {
259
260 before(async function () {
261 this.timeout(60000)
262
263 await servers[0].follows.unfollow({ target: 'root@' + servers[1].host })
264 await waitJobs(servers)
265 })
266
267 it('Should not get the remote video with an unlogged user', async function () {
268 const body = await servers[0].videos.get({ id: video2UUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
269
270 const error = body as unknown as PeerTubeProblemDocument
271 expect(error.code).to.equal(ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS)
272 })
273
274 it('Should get the remote video with a logged in user', async function () {
275 await servers[0].videos.getWithToken({ token: userToken, id: video2UUID })
276 })
277 })
278
279 describe('When following a remote channel', function () {
280
281 before(async function () {
282 this.timeout(60000)
283
284 await servers[0].follows.follow({ handles: [ 'root_channel@' + servers[1].host ] })
285 await waitJobs(servers)
286 })
287
288 it('Should get the remote video with an unlogged user', async function () {
289 await servers[0].videos.get({ id: video2UUID })
290 })
291
292 it('Should get the remote video with a logged in user', async function () {
293 await servers[0].videos.getWithToken({ token: userToken, id: video2UUID })
294 })
295 })
296
297 describe('When unfollowing a remote channel', function () {
298
299 before(async function () {
300 this.timeout(60000)
301
302 await servers[0].follows.unfollow({ target: 'root_channel@' + servers[1].host })
303 await waitJobs(servers)
304 })
305
306 it('Should not get the remote video with an unlogged user', async function () {
307 const body = await servers[0].videos.get({ id: video2UUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
308
309 const error = body as unknown as PeerTubeProblemDocument
310 expect(error.code).to.equal(ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS)
311 })
312
313 it('Should get the remote video with a logged in user', async function () {
314 await servers[0].videos.getWithToken({ token: userToken, id: video2UUID })
315 })
316 })
317
232 after(async function () { 318 after(async function () {
233 await cleanupTests(servers) 319 await cleanupTests(servers)
234 }) 320 })
diff --git a/server/tests/api/server/open-telemetry.ts b/server/tests/api/server/open-telemetry.ts
index 43a27cc32..7a294be82 100644
--- a/server/tests/api/server/open-telemetry.ts
+++ b/server/tests/api/server/open-telemetry.ts
@@ -18,7 +18,7 @@ describe('Open Telemetry', function () {
18 18
19 let hasError = false 19 let hasError = false
20 try { 20 try {
21 await makeRawRequest(metricsUrl, HttpStatusCode.NOT_FOUND_404) 21 await makeRawRequest({ url: metricsUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
22 } catch (err) { 22 } catch (err) {
23 hasError = err.message.includes('ECONNREFUSED') 23 hasError = err.message.includes('ECONNREFUSED')
24 } 24 }
@@ -37,7 +37,7 @@ describe('Open Telemetry', function () {
37 } 37 }
38 }) 38 })
39 39
40 const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200) 40 const res = await makeRawRequest({ url: metricsUrl, expectedStatus: HttpStatusCode.OK_200 })
41 expect(res.text).to.contain('peertube_job_queue_total{') 41 expect(res.text).to.contain('peertube_job_queue_total{')
42 }) 42 })
43 43
@@ -60,7 +60,7 @@ describe('Open Telemetry', function () {
60 } 60 }
61 }) 61 })
62 62
63 const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200) 63 const res = await makeRawRequest({ url: metricsUrl, expectedStatus: HttpStatusCode.OK_200 })
64 expect(res.text).to.contain('peertube_playback_http_downloaded_bytes_total{') 64 expect(res.text).to.contain('peertube_playback_http_downloaded_bytes_total{')
65 }) 65 })
66 66
diff --git a/server/tests/api/server/proxy.ts b/server/tests/api/server/proxy.ts
index a4151ebdd..71c444efd 100644
--- a/server/tests/api/server/proxy.ts
+++ b/server/tests/api/server/proxy.ts
@@ -2,7 +2,7 @@
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { expectNotStartWith, expectStartWith, FIXTURE_URLS, MockProxy } from '@server/tests/shared' 4import { expectNotStartWith, expectStartWith, FIXTURE_URLS, MockProxy } from '@server/tests/shared'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils' 5import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode, VideoPrivacy } from '@shared/models' 6import { HttpStatusCode, VideoPrivacy } from '@shared/models'
7import { 7import {
8 cleanupTests, 8 cleanupTests,
@@ -120,40 +120,40 @@ describe('Test proxy', function () {
120 }) 120 })
121 121
122 describe('Object storage', function () { 122 describe('Object storage', function () {
123 if (areObjectStorageTestsDisabled()) return 123 if (areMockObjectStorageTestsDisabled()) return
124 124
125 before(async function () { 125 before(async function () {
126 this.timeout(30000) 126 this.timeout(30000)
127 127
128 await ObjectStorageCommand.prepareDefaultBuckets() 128 await ObjectStorageCommand.prepareDefaultMockBuckets()
129 }) 129 })
130 130
131 it('Should succeed to upload to object storage with the appropriate proxy config', async function () { 131 it('Should succeed to upload to object storage with the appropriate proxy config', async function () {
132 this.timeout(120000) 132 this.timeout(120000)
133 133
134 await servers[0].kill() 134 await servers[0].kill()
135 await servers[0].run(ObjectStorageCommand.getDefaultConfig(), { env: goodEnv }) 135 await servers[0].run(ObjectStorageCommand.getDefaultMockConfig(), { env: goodEnv })
136 136
137 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) 137 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
138 await waitJobs(servers) 138 await waitJobs(servers)
139 139
140 const video = await servers[0].videos.get({ id: uuid }) 140 const video = await servers[0].videos.get({ id: uuid })
141 141
142 expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 142 expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
143 }) 143 })
144 144
145 it('Should fail to upload to object storage with a wrong proxy config', async function () { 145 it('Should fail to upload to object storage with a wrong proxy config', async function () {
146 this.timeout(120000) 146 this.timeout(120000)
147 147
148 await servers[0].kill() 148 await servers[0].kill()
149 await servers[0].run(ObjectStorageCommand.getDefaultConfig(), { env: badEnv }) 149 await servers[0].run(ObjectStorageCommand.getDefaultMockConfig(), { env: badEnv })
150 150
151 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) 151 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
152 await waitJobs(servers) 152 await waitJobs(servers, { skipDelayed: true })
153 153
154 const video = await servers[0].videos.get({ id: uuid }) 154 const video = await servers[0].videos.get({ id: uuid })
155 155
156 expectNotStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 156 expectNotStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
157 }) 157 })
158 }) 158 })
159 159
diff --git a/server/tests/api/transcoding/audio-only.ts b/server/tests/api/transcoding/audio-only.ts
index 1897c6d6d..b72f5fdbe 100644
--- a/server/tests/api/transcoding/audio-only.ts
+++ b/server/tests/api/transcoding/audio-only.ts
@@ -89,7 +89,12 @@ describe('Test audio only video transcoding', function () {
89 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000) 89 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000)
90 90
91 const size = await getVideoStreamDimensionsInfo(path) 91 const size = await getVideoStreamDimensionsInfo(path)
92 expect(size).to.not.exist 92
93 expect(size.height).to.equal(0)
94 expect(size.width).to.equal(0)
95 expect(size.isPortraitMode).to.be.false
96 expect(size.ratio).to.equal(0)
97 expect(size.resolution).to.equal(0)
93 } 98 }
94 }) 99 })
95 100
diff --git a/server/tests/api/transcoding/create-transcoding.ts b/server/tests/api/transcoding/create-transcoding.ts
index a50bf7654..85389a949 100644
--- a/server/tests/api/transcoding/create-transcoding.ts
+++ b/server/tests/api/transcoding/create-transcoding.ts
@@ -2,7 +2,7 @@
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { checkResolutionsInMasterPlaylist, expectStartWith } from '@server/tests/shared' 4import { checkResolutionsInMasterPlaylist, expectStartWith } from '@server/tests/shared'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils' 5import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode, VideoDetails } from '@shared/models' 6import { HttpStatusCode, VideoDetails } from '@shared/models'
7import { 7import {
8 cleanupTests, 8 cleanupTests,
@@ -19,23 +19,23 @@ import {
19 19
20async function checkFilesInObjectStorage (video: VideoDetails) { 20async function checkFilesInObjectStorage (video: VideoDetails) {
21 for (const file of video.files) { 21 for (const file of video.files) {
22 expectStartWith(file.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 22 expectStartWith(file.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
23 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 23 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
24 } 24 }
25 25
26 if (video.streamingPlaylists.length === 0) return 26 if (video.streamingPlaylists.length === 0) return
27 27
28 const hlsPlaylist = video.streamingPlaylists[0] 28 const hlsPlaylist = video.streamingPlaylists[0]
29 for (const file of hlsPlaylist.files) { 29 for (const file of hlsPlaylist.files) {
30 expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl()) 30 expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
31 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) 31 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
32 } 32 }
33 33
34 expectStartWith(hlsPlaylist.playlistUrl, ObjectStorageCommand.getPlaylistBaseUrl()) 34 expectStartWith(hlsPlaylist.playlistUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
35 await makeRawRequest(hlsPlaylist.playlistUrl, HttpStatusCode.OK_200) 35 await makeRawRequest({ url: hlsPlaylist.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
36 36
37 expectStartWith(hlsPlaylist.segmentsSha256Url, ObjectStorageCommand.getPlaylistBaseUrl()) 37 expectStartWith(hlsPlaylist.segmentsSha256Url, ObjectStorageCommand.getMockPlaylistBaseUrl())
38 await makeRawRequest(hlsPlaylist.segmentsSha256Url, HttpStatusCode.OK_200) 38 await makeRawRequest({ url: hlsPlaylist.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
39} 39}
40 40
41function runTests (objectStorage: boolean) { 41function runTests (objectStorage: boolean) {
@@ -49,7 +49,7 @@ function runTests (objectStorage: boolean) {
49 this.timeout(120000) 49 this.timeout(120000)
50 50
51 const config = objectStorage 51 const config = objectStorage
52 ? ObjectStorageCommand.getDefaultConfig() 52 ? ObjectStorageCommand.getDefaultMockConfig()
53 : {} 53 : {}
54 54
55 // Run server 2 to have transcoding enabled 55 // Run server 2 to have transcoding enabled
@@ -60,7 +60,7 @@ function runTests (objectStorage: boolean) {
60 60
61 await doubleFollow(servers[0], servers[1]) 61 await doubleFollow(servers[0], servers[1])
62 62
63 if (objectStorage) await ObjectStorageCommand.prepareDefaultBuckets() 63 if (objectStorage) await ObjectStorageCommand.prepareDefaultMockBuckets()
64 64
65 const { shortUUID } = await servers[0].videos.quickUpload({ name: 'video' }) 65 const { shortUUID } = await servers[0].videos.quickUpload({ name: 'video' })
66 videoUUID = shortUUID 66 videoUUID = shortUUID
@@ -234,7 +234,7 @@ function runTests (objectStorage: boolean) {
234 234
235 it('Should have correctly deleted previous files', async function () { 235 it('Should have correctly deleted previous files', async function () {
236 for (const fileUrl of shouldBeDeleted) { 236 for (const fileUrl of shouldBeDeleted) {
237 await makeRawRequest(fileUrl, HttpStatusCode.NOT_FOUND_404) 237 await makeRawRequest({ url: fileUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
238 } 238 }
239 }) 239 })
240 240
@@ -256,7 +256,7 @@ describe('Test create transcoding jobs from API', function () {
256 }) 256 })
257 257
258 describe('On object storage', function () { 258 describe('On object storage', function () {
259 if (areObjectStorageTestsDisabled()) return 259 if (areMockObjectStorageTestsDisabled()) return
260 260
261 runTests(true) 261 runTests(true)
262 }) 262 })
diff --git a/server/tests/api/transcoding/hls.ts b/server/tests/api/transcoding/hls.ts
index 252422e5d..84a53c0bd 100644
--- a/server/tests/api/transcoding/hls.ts
+++ b/server/tests/api/transcoding/hls.ts
@@ -1,168 +1,48 @@
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 { join } from 'path'
4import { basename, join } from 'path' 4import { checkDirectoryIsEmpty, checkTmpIsEmpty, completeCheckHlsPlaylist } from '@server/tests/shared'
5import { 5import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
6 checkDirectoryIsEmpty, 6import { HttpStatusCode } from '@shared/models'
7 checkResolutionsInMasterPlaylist,
8 checkSegmentHash,
9 checkTmpIsEmpty,
10 expectStartWith,
11 hlsInfohashExist
12} from '@server/tests/shared'
13import { areObjectStorageTestsDisabled, removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
14import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models'
15import { 7import {
16 cleanupTests, 8 cleanupTests,
17 createMultipleServers, 9 createMultipleServers,
18 doubleFollow, 10 doubleFollow,
19 makeRawRequest,
20 ObjectStorageCommand, 11 ObjectStorageCommand,
21 PeerTubeServer, 12 PeerTubeServer,
22 setAccessTokensToServers, 13 setAccessTokensToServers,
23 waitJobs, 14 waitJobs
24 webtorrentAdd
25} from '@shared/server-commands' 15} from '@shared/server-commands'
26import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' 16import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
27 17
28async function checkHlsPlaylist (options: {
29 servers: PeerTubeServer[]
30 videoUUID: string
31 hlsOnly: boolean
32
33 resolutions?: number[]
34 objectStorageBaseUrl: string
35}) {
36 const { videoUUID, hlsOnly, objectStorageBaseUrl } = options
37
38 const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ]
39
40 for (const server of options.servers) {
41 const videoDetails = await server.videos.get({ id: videoUUID })
42 const baseUrl = `http://${videoDetails.account.host}`
43
44 expect(videoDetails.streamingPlaylists).to.have.lengthOf(1)
45
46 const hlsPlaylist = videoDetails.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
47 expect(hlsPlaylist).to.not.be.undefined
48
49 const hlsFiles = hlsPlaylist.files
50 expect(hlsFiles).to.have.lengthOf(resolutions.length)
51
52 if (hlsOnly) expect(videoDetails.files).to.have.lengthOf(0)
53 else expect(videoDetails.files).to.have.lengthOf(resolutions.length)
54
55 // Check JSON files
56 for (const resolution of resolutions) {
57 const file = hlsFiles.find(f => f.resolution.id === resolution)
58 expect(file).to.not.be.undefined
59
60 expect(file.magnetUri).to.have.lengthOf.above(2)
61 expect(file.torrentUrl).to.match(
62 new RegExp(`http://${server.host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`)
63 )
64
65 if (objectStorageBaseUrl) {
66 expectStartWith(file.fileUrl, objectStorageBaseUrl)
67 } else {
68 expect(file.fileUrl).to.match(
69 new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`)
70 )
71 }
72
73 expect(file.resolution.label).to.equal(resolution + 'p')
74
75 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
76 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200)
77
78 const torrent = await webtorrentAdd(file.magnetUri, true)
79 expect(torrent.files).to.be.an('array')
80 expect(torrent.files.length).to.equal(1)
81 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
82 }
83
84 // Check master playlist
85 {
86 await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
87
88 const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl })
89
90 let i = 0
91 for (const resolution of resolutions) {
92 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
93 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
94
95 const url = 'http://' + videoDetails.account.host
96 await hlsInfohashExist(url, hlsPlaylist.playlistUrl, i)
97
98 i++
99 }
100 }
101
102 // Check resolution playlists
103 {
104 for (const resolution of resolutions) {
105 const file = hlsFiles.find(f => f.resolution.id === resolution)
106 const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8'
107
108 const url = objectStorageBaseUrl
109 ? `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}`
110 : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}`
111
112 const subPlaylist = await server.streamingPlaylists.get({ url })
113
114 expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`))
115 expect(subPlaylist).to.contain(basename(file.fileUrl))
116 }
117 }
118
119 {
120 const baseUrlAndPath = objectStorageBaseUrl
121 ? objectStorageBaseUrl + 'hls/' + videoUUID
122 : baseUrl + '/static/streaming-playlists/hls/' + videoUUID
123
124 for (const resolution of resolutions) {
125 await checkSegmentHash({
126 server,
127 baseUrlPlaylist: baseUrlAndPath,
128 baseUrlSegment: baseUrlAndPath,
129 resolution,
130 hlsPlaylist
131 })
132 }
133 }
134 }
135}
136
137describe('Test HLS videos', function () { 18describe('Test HLS videos', function () {
138 let servers: PeerTubeServer[] = [] 19 let servers: PeerTubeServer[] = []
139 let videoUUID = ''
140 let videoAudioUUID = ''
141 20
142 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) { 21 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) {
22 const videoUUIDs: string[] = []
143 23
144 it('Should upload a video and transcode it to HLS', async function () { 24 it('Should upload a video and transcode it to HLS', async function () {
145 this.timeout(120000) 25 this.timeout(120000)
146 26
147 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 1', fixture: 'video_short.webm' } }) 27 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 1', fixture: 'video_short.webm' } })
148 videoUUID = uuid 28 videoUUIDs.push(uuid)
149 29
150 await waitJobs(servers) 30 await waitJobs(servers)
151 31
152 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl }) 32 await completeCheckHlsPlaylist({ servers, videoUUID: uuid, hlsOnly, objectStorageBaseUrl })
153 }) 33 })
154 34
155 it('Should upload an audio file and transcode it to HLS', async function () { 35 it('Should upload an audio file and transcode it to HLS', async function () {
156 this.timeout(120000) 36 this.timeout(120000)
157 37
158 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video audio', fixture: 'sample.ogg' } }) 38 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video audio', fixture: 'sample.ogg' } })
159 videoAudioUUID = uuid 39 videoUUIDs.push(uuid)
160 40
161 await waitJobs(servers) 41 await waitJobs(servers)
162 42
163 await checkHlsPlaylist({ 43 await completeCheckHlsPlaylist({
164 servers, 44 servers,
165 videoUUID: videoAudioUUID, 45 videoUUID: uuid,
166 hlsOnly, 46 hlsOnly,
167 resolutions: [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ], 47 resolutions: [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ],
168 objectStorageBaseUrl 48 objectStorageBaseUrl
@@ -172,31 +52,36 @@ describe('Test HLS videos', function () {
172 it('Should update the video', async function () { 52 it('Should update the video', async function () {
173 this.timeout(30000) 53 this.timeout(30000)
174 54
175 await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video 1 updated' } }) 55 await servers[0].videos.update({ id: videoUUIDs[0], attributes: { name: 'video 1 updated' } })
176 56
177 await waitJobs(servers) 57 await waitJobs(servers)
178 58
179 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl }) 59 await completeCheckHlsPlaylist({ servers, videoUUID: videoUUIDs[0], hlsOnly, objectStorageBaseUrl })
180 }) 60 })
181 61
182 it('Should delete videos', async function () { 62 it('Should delete videos', async function () {
183 this.timeout(10000) 63 this.timeout(10000)
184 64
185 await servers[0].videos.remove({ id: videoUUID }) 65 for (const uuid of videoUUIDs) {
186 await servers[0].videos.remove({ id: videoAudioUUID }) 66 await servers[0].videos.remove({ id: uuid })
67 }
187 68
188 await waitJobs(servers) 69 await waitJobs(servers)
189 70
190 for (const server of servers) { 71 for (const server of servers) {
191 await server.videos.get({ id: videoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 72 for (const uuid of videoUUIDs) {
192 await server.videos.get({ id: videoAudioUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 73 await server.videos.get({ id: uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
74 }
193 } 75 }
194 }) 76 })
195 77
196 it('Should have the playlists/segment deleted from the disk', async function () { 78 it('Should have the playlists/segment deleted from the disk', async function () {
197 for (const server of servers) { 79 for (const server of servers) {
198 await checkDirectoryIsEmpty(server, 'videos') 80 await checkDirectoryIsEmpty(server, 'videos', [ 'private' ])
199 await checkDirectoryIsEmpty(server, join('streaming-playlists', 'hls')) 81 await checkDirectoryIsEmpty(server, join('videos', 'private'))
82
83 await checkDirectoryIsEmpty(server, join('streaming-playlists', 'hls'), [ 'private' ])
84 await checkDirectoryIsEmpty(server, join('streaming-playlists', 'hls', 'private'))
200 } 85 }
201 }) 86 })
202 87
@@ -265,19 +150,19 @@ describe('Test HLS videos', function () {
265 }) 150 })
266 151
267 describe('With object storage enabled', function () { 152 describe('With object storage enabled', function () {
268 if (areObjectStorageTestsDisabled()) return 153 if (areMockObjectStorageTestsDisabled()) return
269 154
270 before(async function () { 155 before(async function () {
271 this.timeout(120000) 156 this.timeout(120000)
272 157
273 const configOverride = ObjectStorageCommand.getDefaultConfig() 158 const configOverride = ObjectStorageCommand.getDefaultMockConfig()
274 await ObjectStorageCommand.prepareDefaultBuckets() 159 await ObjectStorageCommand.prepareDefaultMockBuckets()
275 160
276 await servers[0].kill() 161 await servers[0].kill()
277 await servers[0].run(configOverride) 162 await servers[0].run(configOverride)
278 }) 163 })
279 164
280 runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl()) 165 runTestSuite(true, ObjectStorageCommand.getMockPlaylistBaseUrl())
281 }) 166 })
282 167
283 after(async function () { 168 after(async function () {
diff --git a/server/tests/api/transcoding/index.ts b/server/tests/api/transcoding/index.ts
index 0cc28b4a4..9866418d6 100644
--- a/server/tests/api/transcoding/index.ts
+++ b/server/tests/api/transcoding/index.ts
@@ -2,4 +2,5 @@ export * from './audio-only'
2export * from './create-transcoding' 2export * from './create-transcoding'
3export * from './hls' 3export * from './hls'
4export * from './transcoder' 4export * from './transcoder'
5export * from './update-while-transcoding'
5export * from './video-studio' 6export * from './video-studio'
diff --git a/server/tests/api/transcoding/update-while-transcoding.ts b/server/tests/api/transcoding/update-while-transcoding.ts
new file mode 100644
index 000000000..8e32ea069
--- /dev/null
+++ b/server/tests/api/transcoding/update-while-transcoding.ts
@@ -0,0 +1,151 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { completeCheckHlsPlaylist } from '@server/tests/shared'
4import { areMockObjectStorageTestsDisabled, wait } from '@shared/core-utils'
5import { VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 ObjectStorageCommand,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 waitJobs
14} from '@shared/server-commands'
15
16describe('Test update video privacy while transcoding', function () {
17 let servers: PeerTubeServer[] = []
18
19 const videoUUIDs: string[] = []
20
21 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) {
22
23 it('Should not have an error while quickly updating a private video to public after upload #1', async function () {
24 this.timeout(360_000)
25
26 const attributes = {
27 name: 'quick update',
28 privacy: VideoPrivacy.PRIVATE
29 }
30
31 const { uuid } = await servers[0].videos.upload({ attributes, waitTorrentGeneration: false })
32 await servers[0].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
33 videoUUIDs.push(uuid)
34
35 await waitJobs(servers)
36
37 await completeCheckHlsPlaylist({ servers, videoUUID: uuid, hlsOnly, objectStorageBaseUrl })
38 })
39
40 it('Should not have an error while quickly updating a private video to public after upload #2', async function () {
41
42 {
43 const attributes = {
44 name: 'quick update 2',
45 privacy: VideoPrivacy.PRIVATE
46 }
47
48 const { uuid } = await servers[0].videos.upload({ attributes, waitTorrentGeneration: true })
49 await servers[0].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
50 videoUUIDs.push(uuid)
51
52 await waitJobs(servers)
53
54 await completeCheckHlsPlaylist({ servers, videoUUID: uuid, hlsOnly, objectStorageBaseUrl })
55 }
56 })
57
58 it('Should not have an error while quickly updating a private video to public after upload #3', async function () {
59 const attributes = {
60 name: 'quick update 3',
61 privacy: VideoPrivacy.PRIVATE
62 }
63
64 const { uuid } = await servers[0].videos.upload({ attributes, waitTorrentGeneration: true })
65 await wait(1000)
66 await servers[0].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
67 videoUUIDs.push(uuid)
68
69 await waitJobs(servers)
70
71 await completeCheckHlsPlaylist({ servers, videoUUID: uuid, hlsOnly, objectStorageBaseUrl })
72 })
73 }
74
75 before(async function () {
76 this.timeout(120000)
77
78 const configOverride = {
79 transcoding: {
80 enabled: true,
81 allow_audio_files: true,
82 hls: {
83 enabled: true
84 }
85 }
86 }
87 servers = await createMultipleServers(2, configOverride)
88
89 // Get the access tokens
90 await setAccessTokensToServers(servers)
91
92 // Server 1 and server 2 follow each other
93 await doubleFollow(servers[0], servers[1])
94 })
95
96 describe('With WebTorrent & HLS enabled', function () {
97 runTestSuite(false)
98 })
99
100 describe('With only HLS enabled', function () {
101
102 before(async function () {
103 await servers[0].config.updateCustomSubConfig({
104 newConfig: {
105 transcoding: {
106 enabled: true,
107 allowAudioFiles: true,
108 resolutions: {
109 '144p': false,
110 '240p': true,
111 '360p': true,
112 '480p': true,
113 '720p': true,
114 '1080p': true,
115 '1440p': true,
116 '2160p': true
117 },
118 hls: {
119 enabled: true
120 },
121 webtorrent: {
122 enabled: false
123 }
124 }
125 }
126 })
127 })
128
129 runTestSuite(true)
130 })
131
132 describe('With object storage enabled', function () {
133 if (areMockObjectStorageTestsDisabled()) return
134
135 before(async function () {
136 this.timeout(120000)
137
138 const configOverride = ObjectStorageCommand.getDefaultMockConfig()
139 await ObjectStorageCommand.prepareDefaultMockBuckets()
140
141 await servers[0].kill()
142 await servers[0].run(configOverride)
143 })
144
145 runTestSuite(true, ObjectStorageCommand.getMockPlaylistBaseUrl())
146 })
147
148 after(async function () {
149 await cleanupTests(servers)
150 })
151})
diff --git a/server/tests/api/transcoding/video-studio.ts b/server/tests/api/transcoding/video-studio.ts
index 9613111b5..ab08e8fb6 100644
--- a/server/tests/api/transcoding/video-studio.ts
+++ b/server/tests/api/transcoding/video-studio.ts
@@ -1,6 +1,6 @@
1import { expect } from 'chai' 1import { expect } from 'chai'
2import { expectStartWith } from '@server/tests/shared' 2import { expectStartWith } from '@server/tests/shared'
3import { areObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils' 3import { areMockObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils'
4import { VideoStudioTask } from '@shared/models' 4import { VideoStudioTask } from '@shared/models'
5import { 5import {
6 cleanupTests, 6 cleanupTests,
@@ -315,13 +315,13 @@ describe('Test video studio', function () {
315 }) 315 })
316 316
317 describe('Object storage video edition', function () { 317 describe('Object storage video edition', function () {
318 if (areObjectStorageTestsDisabled()) return 318 if (areMockObjectStorageTestsDisabled()) return
319 319
320 before(async function () { 320 before(async function () {
321 await ObjectStorageCommand.prepareDefaultBuckets() 321 await ObjectStorageCommand.prepareDefaultMockBuckets()
322 322
323 await servers[0].kill() 323 await servers[0].kill()
324 await servers[0].run(ObjectStorageCommand.getDefaultConfig()) 324 await servers[0].run(ObjectStorageCommand.getDefaultMockConfig())
325 325
326 await servers[0].config.enableMinimumTranscoding() 326 await servers[0].config.enableMinimumTranscoding()
327 }) 327 })
@@ -344,11 +344,11 @@ describe('Test video studio', function () {
344 } 344 }
345 345
346 for (const webtorrentFile of video.files) { 346 for (const webtorrentFile of video.files) {
347 expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl()) 347 expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
348 } 348 }
349 349
350 for (const hlsFile of video.streamingPlaylists[0].files) { 350 for (const hlsFile of video.streamingPlaylists[0].files) {
351 expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl()) 351 expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
352 } 352 }
353 353
354 await checkDuration(server, 9) 354 await checkDuration(server, 9)
diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts
index c65152c6f..643f1a531 100644
--- a/server/tests/api/users/index.ts
+++ b/server/tests/api/users/index.ts
@@ -1,3 +1,4 @@
1import './two-factor'
1import './user-subscriptions' 2import './user-subscriptions'
2import './user-videos' 3import './user-videos'
3import './users' 4import './users'
diff --git a/server/tests/api/users/two-factor.ts b/server/tests/api/users/two-factor.ts
new file mode 100644
index 000000000..0dcab9e17
--- /dev/null
+++ b/server/tests/api/users/two-factor.ts
@@ -0,0 +1,200 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { expectStartWith } from '@server/tests/shared'
5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, TwoFactorCommand } from '@shared/server-commands'
7
8async function login (options: {
9 server: PeerTubeServer
10 username: string
11 password: string
12 otpToken?: string
13 expectedStatus?: HttpStatusCode
14}) {
15 const { server, username, password, otpToken, expectedStatus } = options
16
17 const user = { username, password }
18 const { res, body: { access_token: token } } = await server.login.loginAndGetResponse({ user, otpToken, expectedStatus })
19
20 return { res, token }
21}
22
23describe('Test users', function () {
24 let server: PeerTubeServer
25 let otpSecret: string
26 let requestToken: string
27
28 const userUsername = 'user1'
29 let userId: number
30 let userPassword: string
31 let userToken: string
32
33 before(async function () {
34 this.timeout(30000)
35
36 server = await createSingleServer(1)
37
38 await setAccessTokensToServers([ server ])
39 const res = await server.users.generate(userUsername)
40 userId = res.userId
41 userPassword = res.password
42 userToken = res.token
43 })
44
45 it('Should not add the header on login if two factor is not enabled', async function () {
46 const { res, token } = await login({ server, username: userUsername, password: userPassword })
47
48 expect(res.header['x-peertube-otp']).to.not.exist
49
50 await server.users.getMyInfo({ token })
51 })
52
53 it('Should request two factor and get the secret and uri', async function () {
54 const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword })
55
56 expect(otpRequest.requestToken).to.exist
57
58 expect(otpRequest.secret).to.exist
59 expect(otpRequest.secret).to.have.lengthOf(32)
60
61 expect(otpRequest.uri).to.exist
62 expectStartWith(otpRequest.uri, 'otpauth://')
63 expect(otpRequest.uri).to.include(otpRequest.secret)
64
65 requestToken = otpRequest.requestToken
66 otpSecret = otpRequest.secret
67 })
68
69 it('Should not have two factor confirmed yet', async function () {
70 const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken })
71 expect(twoFactorEnabled).to.be.false
72 })
73
74 it('Should confirm two factor', async function () {
75 await server.twoFactor.confirmRequest({
76 userId,
77 token: userToken,
78 otpToken: TwoFactorCommand.buildOTP({ secret: otpSecret }).generate(),
79 requestToken
80 })
81 })
82
83 it('Should not add the header on login if two factor is enabled and password is incorrect', async function () {
84 const { res, token } = await login({ server, username: userUsername, password: 'fake', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
85
86 expect(res.header['x-peertube-otp']).to.not.exist
87 expect(token).to.not.exist
88 })
89
90 it('Should add the header on login if two factor is enabled and password is correct', async function () {
91 const { res, token } = await login({
92 server,
93 username: userUsername,
94 password: userPassword,
95 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
96 })
97
98 expect(res.header['x-peertube-otp']).to.exist
99 expect(token).to.not.exist
100
101 await server.users.getMyInfo({ token })
102 })
103
104 it('Should not login with correct password and incorrect otp secret', async function () {
105 const otp = TwoFactorCommand.buildOTP({ secret: 'a'.repeat(32) })
106
107 const { res, token } = await login({
108 server,
109 username: userUsername,
110 password: userPassword,
111 otpToken: otp.generate(),
112 expectedStatus: HttpStatusCode.BAD_REQUEST_400
113 })
114
115 expect(res.header['x-peertube-otp']).to.not.exist
116 expect(token).to.not.exist
117 })
118
119 it('Should not login with correct password and incorrect otp code', async function () {
120 const { res, token } = await login({
121 server,
122 username: userUsername,
123 password: userPassword,
124 otpToken: '123456',
125 expectedStatus: HttpStatusCode.BAD_REQUEST_400
126 })
127
128 expect(res.header['x-peertube-otp']).to.not.exist
129 expect(token).to.not.exist
130 })
131
132 it('Should not login with incorrect password and correct otp code', async function () {
133 const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate()
134
135 const { res, token } = await login({
136 server,
137 username: userUsername,
138 password: 'fake',
139 otpToken,
140 expectedStatus: HttpStatusCode.BAD_REQUEST_400
141 })
142
143 expect(res.header['x-peertube-otp']).to.not.exist
144 expect(token).to.not.exist
145 })
146
147 it('Should correctly login with correct password and otp code', async function () {
148 const otpToken = TwoFactorCommand.buildOTP({ secret: otpSecret }).generate()
149
150 const { res, token } = await login({ server, username: userUsername, password: userPassword, otpToken })
151
152 expect(res.header['x-peertube-otp']).to.not.exist
153 expect(token).to.exist
154
155 await server.users.getMyInfo({ token })
156 })
157
158 it('Should have two factor enabled when getting my info', async function () {
159 const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken })
160 expect(twoFactorEnabled).to.be.true
161 })
162
163 it('Should disable two factor and be able to login without otp token', async function () {
164 await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword })
165
166 const { res, token } = await login({ server, username: userUsername, password: userPassword })
167 expect(res.header['x-peertube-otp']).to.not.exist
168
169 await server.users.getMyInfo({ token })
170 })
171
172 it('Should have two factor disabled when getting my info', async function () {
173 const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken })
174 expect(twoFactorEnabled).to.be.false
175 })
176
177 it('Should enable two factor auth without password from an admin', async function () {
178 const { otpRequest } = await server.twoFactor.request({ userId })
179
180 await server.twoFactor.confirmRequest({
181 userId,
182 otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate(),
183 requestToken: otpRequest.requestToken
184 })
185
186 const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken })
187 expect(twoFactorEnabled).to.be.true
188 })
189
190 it('Should disable two factor auth without password from an admin', async function () {
191 await server.twoFactor.disable({ userId })
192
193 const { twoFactorEnabled } = await server.users.getMyInfo({ token: userToken })
194 expect(twoFactorEnabled).to.be.false
195 })
196
197 after(async function () {
198 await cleanupTests([ server ])
199 })
200})
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts
index 62d668d1e..188e6f137 100644
--- a/server/tests/api/users/users-multiple-servers.ts
+++ b/server/tests/api/users/users-multiple-servers.ts
@@ -197,7 +197,7 @@ describe('Test users with multiple servers', function () {
197 it('Should not have actor files', async () => { 197 it('Should not have actor files', async () => {
198 for (const server of servers) { 198 for (const server of servers) {
199 for (const userAvatarFilename of userAvatarFilenames) { 199 for (const userAvatarFilename of userAvatarFilenames) {
200 await checkActorFilesWereRemoved(userAvatarFilename, server.internalServerNumber) 200 await checkActorFilesWereRemoved(userAvatarFilename, server)
201 } 201 }
202 } 202 }
203 }) 203 })
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 9e657b387..421b3ce16 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -181,7 +181,7 @@ describe('Test users', function () {
181 }) 181 })
182 182
183 it('Should refresh the token', async function () { 183 it('Should refresh the token', async function () {
184 this.timeout(15000) 184 this.timeout(50000)
185 185
186 const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString() 186 const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString()
187 await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', futureDate) 187 await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', futureDate)
@@ -219,7 +219,7 @@ describe('Test users', function () {
219 expect(user.email).to.equal('user_1@example.com') 219 expect(user.email).to.equal('user_1@example.com')
220 expect(user.nsfwPolicy).to.equal('display') 220 expect(user.nsfwPolicy).to.equal('display')
221 expect(user.videoQuota).to.equal(2 * 1024 * 1024) 221 expect(user.videoQuota).to.equal(2 * 1024 * 1024)
222 expect(user.roleLabel).to.equal('User') 222 expect(user.role.label).to.equal('User')
223 expect(user.id).to.be.a('number') 223 expect(user.id).to.be.a('number')
224 expect(user.account.displayName).to.equal('user_1') 224 expect(user.account.displayName).to.equal('user_1')
225 expect(user.account.description).to.be.null 225 expect(user.account.description).to.be.null
@@ -277,7 +277,7 @@ describe('Test users', function () {
277 const user = data[0] 277 const user = data[0]
278 expect(user.username).to.equal('root') 278 expect(user.username).to.equal('root')
279 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') 279 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
280 expect(user.roleLabel).to.equal('Administrator') 280 expect(user.role.label).to.equal('Administrator')
281 expect(user.nsfwPolicy).to.equal('display') 281 expect(user.nsfwPolicy).to.equal('display')
282 }) 282 })
283 283
@@ -531,7 +531,7 @@ describe('Test users', function () {
531 expect(user.emailVerified).to.be.true 531 expect(user.emailVerified).to.be.true
532 expect(user.nsfwPolicy).to.equal('do_not_list') 532 expect(user.nsfwPolicy).to.equal('do_not_list')
533 expect(user.videoQuota).to.equal(42) 533 expect(user.videoQuota).to.equal(42)
534 expect(user.roleLabel).to.equal('Moderator') 534 expect(user.role.label).to.equal('Moderator')
535 expect(user.id).to.be.a('number') 535 expect(user.id).to.be.a('number')
536 expect(user.adminFlags).to.equal(UserAdminFlag.NONE) 536 expect(user.adminFlags).to.equal(UserAdminFlag.NONE)
537 expect(user.pluginAuth).to.equal('toto') 537 expect(user.pluginAuth).to.equal('toto')
diff --git a/server/tests/api/videos/channel-import-videos.ts b/server/tests/api/videos/channel-import-videos.ts
index 7cfd02fbb..a66f88a0e 100644
--- a/server/tests/api/videos/channel-import-videos.ts
+++ b/server/tests/api/videos/channel-import-videos.ts
@@ -109,6 +109,45 @@ describe('Test videos import in a channel', function () {
109 } 109 }
110 }) 110 })
111 111
112 it('Should limit max amount of videos synced on full sync', async function () {
113 this.timeout(240_000)
114
115 await server.kill()
116 await server.run({
117 import: {
118 video_channel_synchronization: {
119 full_sync_videos_limit: 1
120 }
121 }
122 })
123
124 const { id } = await server.channels.create({ attributes: { name: 'channel3' } })
125 const channel3Id = id
126
127 const { videoChannelSync } = await server.channelSyncs.create({
128 attributes: {
129 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
130 videoChannelId: channel3Id
131 }
132 })
133 const syncId = videoChannelSync.id
134
135 await waitJobs(server)
136
137 await server.channels.importVideos({
138 channelName: 'channel3',
139 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
140 videoChannelSyncId: syncId
141 })
142
143 await waitJobs(server)
144
145 const { total, data } = await server.videos.listByChannel({ handle: 'channel3' })
146
147 expect(total).to.equal(1)
148 expect(data).to.have.lengthOf(1)
149 })
150
112 after(async function () { 151 after(async function () {
113 await server?.kill() 152 await server?.kill()
114 }) 153 })
@@ -116,5 +155,7 @@ describe('Test videos import in a channel', function () {
116 } 155 }
117 156
118 runSuite('yt-dlp') 157 runSuite('yt-dlp')
119 runSuite('youtube-dl') 158
159 // FIXME: With recent changes on youtube, youtube-dl doesn't fetch live replays which means the test suite fails
160 // runSuite('youtube-dl')
120}) 161})
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts
index 266155297..357c08199 100644
--- a/server/tests/api/videos/index.ts
+++ b/server/tests/api/videos/index.ts
@@ -19,3 +19,4 @@ import './videos-common-filters'
19import './videos-history' 19import './videos-history'
20import './videos-overview' 20import './videos-overview'
21import './video-source' 21import './video-source'
22import './video-static-file-privacy'
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index d47807a79..2ad749fd4 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -156,7 +156,7 @@ describe('Test multiple servers', function () {
156 }) 156 })
157 157
158 it('Should upload the video on server 2 and propagate on each server', async function () { 158 it('Should upload the video on server 2 and propagate on each server', async function () {
159 this.timeout(100000) 159 this.timeout(240000)
160 160
161 const user = { 161 const user = {
162 username: 'user1', 162 username: 'user1',
diff --git a/server/tests/api/videos/video-channel-syncs.ts b/server/tests/api/videos/video-channel-syncs.ts
index 865b25f04..91291524d 100644
--- a/server/tests/api/videos/video-channel-syncs.ts
+++ b/server/tests/api/videos/video-channel-syncs.ts
@@ -220,7 +220,7 @@ describe('Test channel synchronizations', function () {
220 expect(total).to.equal(0) 220 expect(total).to.equal(0)
221 }) 221 })
222 222
223 // FIXME: youtube-dl doesn't work when speicifying a port after the hostname 223 // FIXME: youtube-dl/yt-dlp doesn't work when speicifying a port after the hostname
224 // it('Should import a remote PeerTube channel', async function () { 224 // it('Should import a remote PeerTube channel', async function () {
225 // this.timeout(240_000) 225 // this.timeout(240_000)
226 226
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts
index a4b3ff6e7..c4185882a 100644
--- a/server/tests/api/videos/video-description.ts
+++ b/server/tests/api/videos/video-description.ts
@@ -14,8 +14,12 @@ describe('Test video description', function () {
14 let servers: PeerTubeServer[] = [] 14 let servers: PeerTubeServer[] = []
15 let videoUUID = '' 15 let videoUUID = ''
16 let videoId: number 16 let videoId: number
17
17 const longDescription = 'my super description for server 1'.repeat(50) 18 const longDescription = 'my super description for server 1'.repeat(50)
18 19
20 // 30 characters * 6 -> 240 characters
21 const truncatedDescription = 'my super description for server 1'.repeat(7) + 'my super descrip...'
22
19 before(async function () { 23 before(async function () {
20 this.timeout(40000) 24 this.timeout(40000)
21 25
@@ -45,15 +49,22 @@ describe('Test video description', function () {
45 videoUUID = data[0].uuid 49 videoUUID = data[0].uuid
46 }) 50 })
47 51
48 it('Should have a truncated description on each server', async function () { 52 it('Should have a truncated description on each server when listing videos', async function () {
49 for (const server of servers) { 53 for (const server of servers) {
50 const video = await server.videos.get({ id: videoUUID }) 54 const { data } = await server.videos.list()
51 55 const video = data.find(v => v.uuid === videoUUID)
52 // 30 characters * 6 -> 240 characters
53 const truncatedDescription = 'my super description for server 1'.repeat(7) +
54 'my super descrip...'
55 56
56 expect(video.description).to.equal(truncatedDescription) 57 expect(video.description).to.equal(truncatedDescription)
58 expect(video.truncatedDescription).to.equal(truncatedDescription)
59 }
60 })
61
62 it('Should not have a truncated description on each server when getting videos', async function () {
63 for (const server of servers) {
64 const video = await server.videos.get({ id: videoUUID })
65
66 expect(video.description).to.equal(longDescription)
67 expect(video.truncatedDescription).to.equal(truncatedDescription)
57 } 68 }
58 }) 69 })
59 70
diff --git a/server/tests/api/videos/video-files.ts b/server/tests/api/videos/video-files.ts
index 10277b9cf..8c913bf31 100644
--- a/server/tests/api/videos/video-files.ts
+++ b/server/tests/api/videos/video-files.ts
@@ -33,7 +33,7 @@ describe('Test videos files', function () {
33 let validId2: string 33 let validId2: string
34 34
35 before(async function () { 35 before(async function () {
36 this.timeout(120_000) 36 this.timeout(360_000)
37 37
38 { 38 {
39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1' }) 39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1' })
@@ -153,7 +153,7 @@ describe('Test videos files', function () {
153 expect(video.streamingPlaylists[0].files).to.have.lengthOf(files.length - 1) 153 expect(video.streamingPlaylists[0].files).to.have.lengthOf(files.length - 1)
154 expect(video.streamingPlaylists[0].files.find(f => f.id === toDelete.id)).to.not.exist 154 expect(video.streamingPlaylists[0].files.find(f => f.id === toDelete.id)).to.not.exist
155 155
156 const { text } = await makeRawRequest(video.streamingPlaylists[0].playlistUrl) 156 const { text } = await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
157 157
158 expect(text.includes(`-${toDelete.resolution.id}.m3u8`)).to.be.false 158 expect(text.includes(`-${toDelete.resolution.id}.m3u8`)).to.be.false
159 expect(text.includes(`-${video.streamingPlaylists[0].files[0].resolution.id}.m3u8`)).to.be.true 159 expect(text.includes(`-${video.streamingPlaylists[0].files[0].resolution.id}.m3u8`)).to.be.true
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 47b8c7b1e..a3de73ba5 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -23,6 +23,7 @@ import {
23 setDefaultVideoChannel, 23 setDefaultVideoChannel,
24 waitJobs 24 waitJobs
25} from '@shared/server-commands' 25} from '@shared/server-commands'
26import { uuidToShort } from '@shared/extra-utils'
26 27
27async function checkPlaylistElementType ( 28async function checkPlaylistElementType (
28 servers: PeerTubeServer[], 29 servers: PeerTubeServer[],
@@ -56,6 +57,7 @@ describe('Test video playlists', function () {
56 let playlistServer2UUID2: string 57 let playlistServer2UUID2: string
57 58
58 let playlistServer1Id: number 59 let playlistServer1Id: number
60 let playlistServer1DisplayName: string
59 let playlistServer1UUID: string 61 let playlistServer1UUID: string
60 let playlistServer1UUID2: string 62 let playlistServer1UUID2: string
61 63
@@ -70,7 +72,7 @@ describe('Test video playlists', function () {
70 let commands: PlaylistsCommand[] 72 let commands: PlaylistsCommand[]
71 73
72 before(async function () { 74 before(async function () {
73 this.timeout(120000) 75 this.timeout(240000)
74 76
75 servers = await createMultipleServers(3) 77 servers = await createMultipleServers(3)
76 78
@@ -489,15 +491,17 @@ describe('Test video playlists', function () {
489 return commands[0].addElement({ playlistId: playlistServer1Id, attributes }) 491 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
490 } 492 }
491 493
494 const playlistDisplayName = 'playlist 4'
492 const playlist = await commands[0].create({ 495 const playlist = await commands[0].create({
493 attributes: { 496 attributes: {
494 displayName: 'playlist 4', 497 displayName: playlistDisplayName,
495 privacy: VideoPlaylistPrivacy.PUBLIC, 498 privacy: VideoPlaylistPrivacy.PUBLIC,
496 videoChannelId: servers[0].store.channel.id 499 videoChannelId: servers[0].store.channel.id
497 } 500 }
498 }) 501 })
499 502
500 playlistServer1Id = playlist.id 503 playlistServer1Id = playlist.id
504 playlistServer1DisplayName = playlistDisplayName
501 playlistServer1UUID = playlist.uuid 505 playlistServer1UUID = playlist.uuid
502 506
503 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) 507 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
@@ -908,6 +912,8 @@ describe('Test video playlists', function () {
908 const elem = obj[servers[0].store.videos[0].id] 912 const elem = obj[servers[0].store.videos[0].id]
909 expect(elem).to.have.lengthOf(1) 913 expect(elem).to.have.lengthOf(1)
910 expect(elem[0].playlistElementId).to.exist 914 expect(elem[0].playlistElementId).to.exist
915 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
916 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
911 expect(elem[0].playlistId).to.equal(playlistServer1Id) 917 expect(elem[0].playlistId).to.equal(playlistServer1Id)
912 expect(elem[0].startTimestamp).to.equal(15) 918 expect(elem[0].startTimestamp).to.equal(15)
913 expect(elem[0].stopTimestamp).to.equal(28) 919 expect(elem[0].stopTimestamp).to.equal(28)
@@ -917,6 +923,8 @@ describe('Test video playlists', function () {
917 const elem = obj[servers[0].store.videos[3].id] 923 const elem = obj[servers[0].store.videos[3].id]
918 expect(elem).to.have.lengthOf(1) 924 expect(elem).to.have.lengthOf(1)
919 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4) 925 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
926 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
927 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
920 expect(elem[0].playlistId).to.equal(playlistServer1Id) 928 expect(elem[0].playlistId).to.equal(playlistServer1Id)
921 expect(elem[0].startTimestamp).to.equal(1) 929 expect(elem[0].startTimestamp).to.equal(1)
922 expect(elem[0].stopTimestamp).to.equal(35) 930 expect(elem[0].stopTimestamp).to.equal(35)
@@ -926,6 +934,8 @@ describe('Test video playlists', function () {
926 const elem = obj[servers[0].store.videos[4].id] 934 const elem = obj[servers[0].store.videos[4].id]
927 expect(elem).to.have.lengthOf(1) 935 expect(elem).to.have.lengthOf(1)
928 expect(elem[0].playlistId).to.equal(playlistServer1Id) 936 expect(elem[0].playlistId).to.equal(playlistServer1Id)
937 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
938 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
929 expect(elem[0].startTimestamp).to.equal(45) 939 expect(elem[0].startTimestamp).to.equal(45)
930 expect(elem[0].stopTimestamp).to.equal(null) 940 expect(elem[0].stopTimestamp).to.equal(null)
931 } 941 }
@@ -1049,7 +1059,7 @@ describe('Test video playlists', function () {
1049 this.timeout(30000) 1059 this.timeout(30000)
1050 1060
1051 for (const server of servers) { 1061 for (const server of servers) {
1052 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber) 1062 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server)
1053 } 1063 }
1054 }) 1064 })
1055 1065
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index b18c71c94..264a05d3f 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -45,7 +45,7 @@ describe('Test video privacy', function () {
45 describe('Private and internal videos', function () { 45 describe('Private and internal videos', function () {
46 46
47 it('Should upload a private and internal videos on server 1', async function () { 47 it('Should upload a private and internal videos on server 1', async function () {
48 this.timeout(10000) 48 this.timeout(50000)
49 49
50 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) { 50 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
51 const attributes = { privacy } 51 const attributes = { privacy }
@@ -128,7 +128,7 @@ describe('Test video privacy', function () {
128 describe('Unlisted videos', function () { 128 describe('Unlisted videos', function () {
129 129
130 it('Should upload an unlisted video on server 2', async function () { 130 it('Should upload an unlisted video on server 2', async function () {
131 this.timeout(60000) 131 this.timeout(120000)
132 132
133 const attributes = { 133 const attributes = {
134 name: 'unlisted video', 134 name: 'unlisted video',
diff --git a/server/tests/api/videos/video-static-file-privacy.ts b/server/tests/api/videos/video-static-file-privacy.ts
new file mode 100644
index 000000000..eaaed5aad
--- /dev/null
+++ b/server/tests/api/videos/video-static-file-privacy.ts
@@ -0,0 +1,422 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { decode } from 'magnet-uri'
5import { expectStartWith } from '@server/tests/shared'
6import { getAllFiles, wait } from '@shared/core-utils'
7import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models'
8import {
9 cleanupTests,
10 createSingleServer,
11 findExternalSavedVideo,
12 makeRawRequest,
13 parseTorrentVideo,
14 PeerTubeServer,
15 sendRTMPStream,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 stopFfmpeg,
19 waitJobs
20} from '@shared/server-commands'
21
22describe('Test video static file privacy', function () {
23 let server: PeerTubeServer
24 let userToken: string
25
26 before(async function () {
27 this.timeout(50000)
28
29 server = await createSingleServer(1)
30 await setAccessTokensToServers([ server ])
31 await setDefaultVideoChannel([ server ])
32
33 userToken = await server.users.generateUserAndToken('user1')
34 })
35
36 describe('VOD static file path', function () {
37
38 function runSuite () {
39
40 async function checkPrivateFiles (uuid: string) {
41 const video = await server.videos.getWithToken({ id: uuid })
42
43 for (const file of video.files) {
44 expect(file.fileDownloadUrl).to.not.include('/private/')
45 expectStartWith(file.fileUrl, server.url + '/static/webseed/private/')
46
47 const torrent = await parseTorrentVideo(server, file)
48 expect(torrent.urlList).to.have.lengthOf(0)
49
50 const magnet = decode(file.magnetUri)
51 expect(magnet.urlList).to.have.lengthOf(0)
52
53 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
54 }
55
56 const hls = video.streamingPlaylists[0]
57 if (hls) {
58 expectStartWith(hls.playlistUrl, server.url + '/static/streaming-playlists/hls/private/')
59 expectStartWith(hls.segmentsSha256Url, server.url + '/static/streaming-playlists/hls/private/')
60
61 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
62 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
63 }
64 }
65
66 async function checkPublicFiles (uuid: string) {
67 const video = await server.videos.get({ id: uuid })
68
69 for (const file of getAllFiles(video)) {
70 expect(file.fileDownloadUrl).to.not.include('/private/')
71 expect(file.fileUrl).to.not.include('/private/')
72
73 const torrent = await parseTorrentVideo(server, file)
74 expect(torrent.urlList[0]).to.not.include('private')
75
76 const magnet = decode(file.magnetUri)
77 expect(magnet.urlList[0]).to.not.include('private')
78
79 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
80 await makeRawRequest({ url: torrent.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
81 await makeRawRequest({ url: magnet.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
82 }
83
84 const hls = video.streamingPlaylists[0]
85 if (hls) {
86 expect(hls.playlistUrl).to.not.include('private')
87 expect(hls.segmentsSha256Url).to.not.include('private')
88
89 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
90 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
91 }
92 }
93
94 it('Should upload a private/internal video and have a private static path', async function () {
95 this.timeout(120000)
96
97 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
98 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy })
99 await waitJobs([ server ])
100
101 await checkPrivateFiles(uuid)
102 }
103 })
104
105 it('Should upload a public video and update it as private/internal to have a private static path', async function () {
106 this.timeout(120000)
107
108 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
109 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PUBLIC })
110 await waitJobs([ server ])
111
112 await server.videos.update({ id: uuid, attributes: { privacy } })
113 await waitJobs([ server ])
114
115 await checkPrivateFiles(uuid)
116 }
117 })
118
119 it('Should upload a private video and update it to unlisted to have a public static path', async function () {
120 this.timeout(120000)
121
122 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
123 await waitJobs([ server ])
124
125 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
126 await waitJobs([ server ])
127
128 await checkPublicFiles(uuid)
129 })
130
131 it('Should upload an internal video and update it to public to have a public static path', async function () {
132 this.timeout(120000)
133
134 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
135 await waitJobs([ server ])
136
137 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
138 await waitJobs([ server ])
139
140 await checkPublicFiles(uuid)
141 })
142
143 it('Should upload an internal video and schedule a public publish', async function () {
144 this.timeout(120000)
145
146 const attributes = {
147 name: 'video',
148 privacy: VideoPrivacy.PRIVATE,
149 scheduleUpdate: {
150 updateAt: new Date(Date.now() + 1000).toISOString(),
151 privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC
152 }
153 }
154
155 const { uuid } = await server.videos.upload({ attributes })
156
157 await waitJobs([ server ])
158 await wait(1000)
159 await server.debug.sendCommand({ body: { command: 'process-update-videos-scheduler' } })
160
161 await waitJobs([ server ])
162
163 await checkPublicFiles(uuid)
164 })
165 }
166
167 describe('Without transcoding', function () {
168 runSuite()
169 })
170
171 describe('With transcoding', function () {
172
173 before(async function () {
174 await server.config.enableMinimumTranscoding()
175 })
176
177 runSuite()
178 })
179 })
180
181 describe('VOD static file right check', function () {
182 let unrelatedFileToken: string
183
184 async function checkVideoFiles (options: {
185 id: string
186 expectedStatus: HttpStatusCode
187 token: string
188 videoFileToken: string
189 }) {
190 const { id, expectedStatus, token, videoFileToken } = options
191
192 const video = await server.videos.getWithToken({ id })
193
194 for (const file of getAllFiles(video)) {
195 await makeRawRequest({ url: file.fileUrl, token, expectedStatus })
196 await makeRawRequest({ url: file.fileDownloadUrl, token, expectedStatus })
197
198 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken }, expectedStatus })
199 await makeRawRequest({ url: file.fileDownloadUrl, query: { videoFileToken }, expectedStatus })
200 }
201
202 const hls = video.streamingPlaylists[0]
203 await makeRawRequest({ url: hls.playlistUrl, token, expectedStatus })
204 await makeRawRequest({ url: hls.segmentsSha256Url, token, expectedStatus })
205
206 await makeRawRequest({ url: hls.playlistUrl, query: { videoFileToken }, expectedStatus })
207 await makeRawRequest({ url: hls.segmentsSha256Url, query: { videoFileToken }, expectedStatus })
208 }
209
210 before(async function () {
211 await server.config.enableMinimumTranscoding()
212
213 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
214 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
215 })
216
217 it('Should not be able to access a private video files without OAuth token and file token', async function () {
218 this.timeout(120000)
219
220 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
221 await waitJobs([ server ])
222
223 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: null, videoFileToken: null })
224 })
225
226 it('Should not be able to access an internal video files without appropriate OAuth token and file token', async function () {
227 this.timeout(120000)
228
229 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
230 await waitJobs([ server ])
231
232 await checkVideoFiles({
233 id: uuid,
234 expectedStatus: HttpStatusCode.FORBIDDEN_403,
235 token: userToken,
236 videoFileToken: unrelatedFileToken
237 })
238 })
239
240 it('Should be able to access a private video files with appropriate OAuth token or file token', async function () {
241 this.timeout(120000)
242
243 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
244 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
245
246 await waitJobs([ server ])
247
248 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
249 })
250
251 it('Should be able to access a private video of another user with an admin OAuth token or file token', async function () {
252 this.timeout(120000)
253
254 const { uuid } = await server.videos.quickUpload({ name: 'video', token: userToken, privacy: VideoPrivacy.PRIVATE })
255 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
256
257 await waitJobs([ server ])
258
259 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
260 })
261 })
262
263 describe('Live static file path and check', function () {
264 let normalLiveId: string
265 let normalLive: LiveVideo
266
267 let permanentLiveId: string
268 let permanentLive: LiveVideo
269
270 let unrelatedFileToken: string
271
272 async function checkLiveFiles (live: LiveVideo, liveId: string) {
273 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
274 await server.live.waitUntilPublished({ videoId: liveId })
275
276 const video = await server.videos.getWithToken({ id: liveId })
277 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
278
279 const hls = video.streamingPlaylists[0]
280
281 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
282 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
283
284 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
285 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
286
287 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
288 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
289 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
290 }
291
292 await stopFfmpeg(ffmpegCommand)
293 }
294
295 async function checkReplay (replay: VideoDetails) {
296 const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
297
298 const hls = replay.streamingPlaylists[0]
299 expect(hls.files).to.not.have.lengthOf(0)
300
301 for (const file of hls.files) {
302 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
303 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
304
305 await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
306 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
307 await makeRawRequest({
308 url: file.fileUrl,
309 query: { videoFileToken: unrelatedFileToken },
310 expectedStatus: HttpStatusCode.FORBIDDEN_403
311 })
312 }
313
314 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
315 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
316
317 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
318 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
319
320 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
321 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
322 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
323 }
324 }
325
326 before(async function () {
327 await server.config.enableMinimumTranscoding()
328
329 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
330 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
331
332 await server.config.enableLive({
333 allowReplay: true,
334 transcoding: true,
335 resolutions: 'min'
336 })
337
338 {
339 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: false, privacy: VideoPrivacy.PRIVATE })
340 normalLiveId = video.uuid
341 normalLive = live
342 }
343
344 {
345 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: true, privacy: VideoPrivacy.PRIVATE })
346 permanentLiveId = video.uuid
347 permanentLive = live
348 }
349 })
350
351 it('Should create a private normal live and have a private static path', async function () {
352 this.timeout(240000)
353
354 await checkLiveFiles(normalLive, normalLiveId)
355 })
356
357 it('Should create a private permanent live and have a private static path', async function () {
358 this.timeout(240000)
359
360 await checkLiveFiles(permanentLive, permanentLiveId)
361 })
362
363 it('Should have created a replay of the normal live with a private static path', async function () {
364 this.timeout(240000)
365
366 await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
367
368 const replay = await server.videos.getWithToken({ id: normalLiveId })
369 await checkReplay(replay)
370 })
371
372 it('Should have created a replay of the permanent live with a private static path', async function () {
373 this.timeout(240000)
374
375 await server.live.waitUntilWaiting({ videoId: permanentLiveId })
376 await waitJobs([ server ])
377
378 const live = await server.videos.getWithToken({ id: permanentLiveId })
379 const replayFromList = await findExternalSavedVideo(server, live)
380 const replay = await server.videos.getWithToken({ id: replayFromList.id })
381
382 await checkReplay(replay)
383 })
384 })
385
386 describe('With static file right check disabled', function () {
387 let videoUUID: string
388
389 before(async function () {
390 this.timeout(240000)
391
392 await server.kill()
393
394 await server.run({
395 static_files: {
396 private_files_require_auth: false
397 }
398 })
399
400 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
401 videoUUID = uuid
402
403 await waitJobs([ server ])
404 })
405
406 it('Should not check auth for private static files', async function () {
407 const video = await server.videos.getWithToken({ id: videoUUID })
408
409 for (const file of getAllFiles(video)) {
410 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
411 }
412
413 const hls = video.streamingPlaylists[0]
414 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
415 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
416 })
417 })
418
419 after(async function () {
420 await cleanupTests([ server ])
421 })
422})
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
index e7fc15e42..b176d90ab 100644
--- a/server/tests/api/videos/videos-common-filters.ts
+++ b/server/tests/api/videos/videos-common-filters.ts
@@ -232,7 +232,7 @@ describe('Test videos filter', function () {
232 }) 232 })
233 233
234 it('Should display only remote videos', async function () { 234 it('Should display only remote videos', async function () {
235 this.timeout(40000) 235 this.timeout(120000)
236 236
237 await servers[1].videos.upload({ attributes: { name: 'remote video' } }) 237 await servers[1].videos.upload({ attributes: { name: 'remote video' } })
238 238
diff --git a/server/tests/api/views/video-views-counter.ts b/server/tests/api/views/video-views-counter.ts
index ca33ff9cd..0c1b7859c 100644
--- a/server/tests/api/views/video-views-counter.ts
+++ b/server/tests/api/views/video-views-counter.ts
@@ -76,7 +76,7 @@ describe('Test video views/viewers counters', function () {
76 let command: FfmpegCommand 76 let command: FfmpegCommand
77 77
78 before(async function () { 78 before(async function () {
79 this.timeout(120000); 79 this.timeout(240000);
80 80
81 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true })) 81 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
82 }) 82 })
diff --git a/server/tests/api/views/video-views-overall-stats.ts b/server/tests/api/views/video-views-overall-stats.ts
index bb00684ef..3aadc9689 100644
--- a/server/tests/api/views/video-views-overall-stats.ts
+++ b/server/tests/api/views/video-views-overall-stats.ts
@@ -20,7 +20,7 @@ describe('Test views overall stats', function () {
20 let command: FfmpegCommand 20 let command: FfmpegCommand
21 21
22 before(async function () { 22 before(async function () {
23 this.timeout(120000); 23 this.timeout(240000);
24 24
25 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true })) 25 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
26 }) 26 })
@@ -179,7 +179,7 @@ describe('Test views overall stats', function () {
179 let before2Watchers: Date 179 let before2Watchers: Date
180 180
181 before(async function () { 181 before(async function () {
182 this.timeout(120000); 182 this.timeout(240000);
183 183
184 ({ vodVideoId: videoUUID } = await prepareViewsVideos({ servers, live: true, vod: true })) 184 ({ vodVideoId: videoUUID } = await prepareViewsVideos({ servers, live: true, vod: true }))
185 }) 185 })
diff --git a/server/tests/api/views/video-views-retention-stats.ts b/server/tests/api/views/video-views-retention-stats.ts
index 621b05110..5b9ce4c92 100644
--- a/server/tests/api/views/video-views-retention-stats.ts
+++ b/server/tests/api/views/video-views-retention-stats.ts
@@ -17,7 +17,7 @@ describe('Test views retention stats', function () {
17 let vodVideoId: string 17 let vodVideoId: string
18 18
19 before(async function () { 19 before(async function () {
20 this.timeout(120000); 20 this.timeout(240000);
21 21
22 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true })) 22 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true }))
23 }) 23 })
diff --git a/server/tests/api/views/video-views-timeserie-stats.ts b/server/tests/api/views/video-views-timeserie-stats.ts
index e8cb34ad6..2d991d7ea 100644
--- a/server/tests/api/views/video-views-timeserie-stats.ts
+++ b/server/tests/api/views/video-views-timeserie-stats.ts
@@ -30,7 +30,7 @@ describe('Test views timeserie stats', function () {
30 let vodVideoId: string 30 let vodVideoId: string
31 31
32 before(async function () { 32 before(async function () {
33 this.timeout(120000); 33 this.timeout(240000);
34 34
35 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true })) 35 ({ vodVideoId } = await prepareViewsVideos({ servers, live: false, vod: true }))
36 }) 36 })
@@ -81,7 +81,7 @@ describe('Test views timeserie stats', function () {
81 } 81 }
82 82
83 before(async function () { 83 before(async function () {
84 this.timeout(120000); 84 this.timeout(240000);
85 85
86 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true })) 86 ({ vodVideoId, liveVideoId, ffmpegCommand: command } = await prepareViewsVideos({ servers, live: true, vod: true }))
87 }) 87 })