aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/videos
diff options
context:
space:
mode:
Diffstat (limited to 'server/tests/api/videos')
-rw-r--r--server/tests/api/videos/channel-import-videos.ts161
-rw-r--r--server/tests/api/videos/index.ts23
-rw-r--r--server/tests/api/videos/multiple-servers.ts1099
-rw-r--r--server/tests/api/videos/resumable-upload.ts310
-rw-r--r--server/tests/api/videos/single-server.ts460
-rw-r--r--server/tests/api/videos/video-captions.ts188
-rw-r--r--server/tests/api/videos/video-change-ownership.ts314
-rw-r--r--server/tests/api/videos/video-channel-syncs.ts320
-rw-r--r--server/tests/api/videos/video-channels.ts555
-rw-r--r--server/tests/api/videos/video-comments.ts335
-rw-r--r--server/tests/api/videos/video-description.ts103
-rw-r--r--server/tests/api/videos/video-files.ts202
-rw-r--r--server/tests/api/videos/video-imports.ts631
-rw-r--r--server/tests/api/videos/video-nsfw.ts227
-rw-r--r--server/tests/api/videos/video-passwords.ts97
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts234
-rw-r--r--server/tests/api/videos/video-playlists.ts1208
-rw-r--r--server/tests/api/videos/video-privacy.ts287
-rw-r--r--server/tests/api/videos/video-schedule-update.ts155
-rw-r--r--server/tests/api/videos/video-source.ts447
-rw-r--r--server/tests/api/videos/video-static-file-privacy.ts600
-rw-r--r--server/tests/api/videos/video-storyboard.ts213
-rw-r--r--server/tests/api/videos/videos-common-filters.ts489
-rw-r--r--server/tests/api/videos/videos-history.ts224
-rw-r--r--server/tests/api/videos/videos-overview.ts129
25 files changed, 0 insertions, 9011 deletions
diff --git a/server/tests/api/videos/channel-import-videos.ts b/server/tests/api/videos/channel-import-videos.ts
deleted file mode 100644
index a66f88a0e..000000000
--- a/server/tests/api/videos/channel-import-videos.ts
+++ /dev/null
@@ -1,161 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { FIXTURE_URLS } from '@server/tests/shared'
5import { areHttpImportTestsDisabled } from '@shared/core-utils'
6import {
7 createSingleServer,
8 getServerImportConfig,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 setDefaultVideoChannel,
12 waitJobs
13} from '@shared/server-commands'
14
15describe('Test videos import in a channel', function () {
16 if (areHttpImportTestsDisabled()) return
17
18 function runSuite (mode: 'youtube-dl' | 'yt-dlp') {
19
20 describe('Import using ' + mode, function () {
21 let server: PeerTubeServer
22
23 before(async function () {
24 this.timeout(120_000)
25
26 server = await createSingleServer(1, getServerImportConfig(mode))
27
28 await setAccessTokensToServers([ server ])
29 await setDefaultVideoChannel([ server ])
30
31 await server.config.enableChannelSync()
32 })
33
34 it('Should import a whole channel without specifying the sync id', async function () {
35 this.timeout(240_000)
36
37 await server.channels.importVideos({ channelName: server.store.channel.name, externalChannelUrl: FIXTURE_URLS.youtubeChannel })
38 await waitJobs(server)
39
40 const videos = await server.videos.listByChannel({ handle: server.store.channel.name })
41 expect(videos.total).to.equal(2)
42 })
43
44 it('These imports should not have a sync id', async function () {
45 const { total, data } = await server.imports.getMyVideoImports()
46
47 expect(total).to.equal(2)
48 expect(data).to.have.lengthOf(2)
49
50 for (const videoImport of data) {
51 expect(videoImport.videoChannelSync).to.not.exist
52 }
53 })
54
55 it('Should import a whole channel and specifying the sync id', async function () {
56 this.timeout(240_000)
57
58 {
59 server.store.channel.name = 'channel2'
60 const { id } = await server.channels.create({ attributes: { name: server.store.channel.name } })
61 server.store.channel.id = id
62 }
63
64 {
65 const attributes = {
66 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
67 videoChannelId: server.store.channel.id
68 }
69
70 const { videoChannelSync } = await server.channelSyncs.create({ attributes })
71 server.store.videoChannelSync = videoChannelSync
72
73 await waitJobs(server)
74 }
75
76 await server.channels.importVideos({
77 channelName: server.store.channel.name,
78 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
79 videoChannelSyncId: server.store.videoChannelSync.id
80 })
81
82 await waitJobs(server)
83 })
84
85 it('These imports should have a sync id', async function () {
86 const { total, data } = await server.imports.getMyVideoImports()
87
88 expect(total).to.equal(4)
89 expect(data).to.have.lengthOf(4)
90
91 const importsWithSyncId = data.filter(i => !!i.videoChannelSync)
92 expect(importsWithSyncId).to.have.lengthOf(2)
93
94 for (const videoImport of importsWithSyncId) {
95 expect(videoImport.videoChannelSync).to.exist
96 expect(videoImport.videoChannelSync.id).to.equal(server.store.videoChannelSync.id)
97 }
98 })
99
100 it('Should be able to filter imports by this sync id', async function () {
101 const { total, data } = await server.imports.getMyVideoImports({ videoChannelSyncId: server.store.videoChannelSync.id })
102
103 expect(total).to.equal(2)
104 expect(data).to.have.lengthOf(2)
105
106 for (const videoImport of data) {
107 expect(videoImport.videoChannelSync).to.exist
108 expect(videoImport.videoChannelSync.id).to.equal(server.store.videoChannelSync.id)
109 }
110 })
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
151 after(async function () {
152 await server?.kill()
153 })
154 })
155 }
156
157 runSuite('yt-dlp')
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')
161})
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts
deleted file mode 100644
index 01d0c5852..000000000
--- a/server/tests/api/videos/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
1import './multiple-servers'
2import './resumable-upload'
3import './single-server'
4import './video-captions'
5import './video-change-ownership'
6import './video-channels'
7import './channel-import-videos'
8import './video-channel-syncs'
9import './video-comments'
10import './video-description'
11import './video-files'
12import './video-imports'
13import './video-nsfw'
14import './video-playlists'
15import './video-playlist-thumbnails'
16import './video-source'
17import './video-privacy'
18import './video-schedule-update'
19import './videos-common-filters'
20import './videos-history'
21import './videos-overview'
22import './video-static-file-privacy'
23import './video-storyboard'
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
deleted file mode 100644
index e9aa0e3a1..000000000
--- a/server/tests/api/videos/multiple-servers.ts
+++ /dev/null
@@ -1,1099 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import request from 'supertest'
5import {
6 checkTmpIsEmpty,
7 checkVideoFilesWereRemoved,
8 checkWebTorrentWorks,
9 completeVideoCheck,
10 dateIsValid,
11 saveVideoInServers,
12 testImageGeneratedByFFmpeg
13} from '@server/tests/shared'
14import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
15import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
16import {
17 cleanupTests,
18 createMultipleServers,
19 doubleFollow,
20 makeGetRequest,
21 PeerTubeServer,
22 setAccessTokensToServers,
23 setDefaultAccountAvatar,
24 setDefaultChannelAvatar,
25 waitJobs
26} from '@shared/server-commands'
27
28describe('Test multiple servers', function () {
29 let servers: PeerTubeServer[] = []
30 const toRemove = []
31 let videoUUID = ''
32 let videoChannelId: number
33
34 before(async function () {
35 this.timeout(120000)
36
37 servers = await createMultipleServers(3)
38
39 // Get the access tokens
40 await setAccessTokensToServers(servers)
41
42 {
43 const videoChannel = {
44 name: 'super_channel_name',
45 displayName: 'my channel',
46 description: 'super channel'
47 }
48 await servers[0].channels.create({ attributes: videoChannel })
49 await setDefaultChannelAvatar(servers[0], videoChannel.name)
50 await setDefaultAccountAvatar(servers)
51
52 const { data } = await servers[0].channels.list({ start: 0, count: 1 })
53 videoChannelId = data[0].id
54 }
55
56 // Server 1 and server 2 follow each other
57 await doubleFollow(servers[0], servers[1])
58 // Server 1 and server 3 follow each other
59 await doubleFollow(servers[0], servers[2])
60 // Server 2 and server 3 follow each other
61 await doubleFollow(servers[1], servers[2])
62 })
63
64 it('Should not have videos for all servers', async function () {
65 for (const server of servers) {
66 const { data } = await server.videos.list()
67 expect(data).to.be.an('array')
68 expect(data.length).to.equal(0)
69 }
70 })
71
72 describe('Should upload the video and propagate on each server', function () {
73
74 it('Should upload the video on server 1 and propagate on each server', async function () {
75 this.timeout(60000)
76
77 const attributes = {
78 name: 'my super name for server 1',
79 category: 5,
80 licence: 4,
81 language: 'ja',
82 nsfw: true,
83 description: 'my super description for server 1',
84 support: 'my super support text for server 1',
85 originallyPublishedAt: '2019-02-10T13:38:14.449Z',
86 tags: [ 'tag1p1', 'tag2p1' ],
87 channelId: videoChannelId,
88 fixture: 'video_short1.webm'
89 }
90 await servers[0].videos.upload({ attributes })
91
92 await waitJobs(servers)
93
94 // All servers should have this video
95 let publishedAt: string = null
96 for (const server of servers) {
97 const isLocal = server.port === servers[0].port
98 const checkAttributes = {
99 name: 'my super name for server 1',
100 category: 5,
101 licence: 4,
102 language: 'ja',
103 nsfw: true,
104 description: 'my super description for server 1',
105 support: 'my super support text for server 1',
106 originallyPublishedAt: '2019-02-10T13:38:14.449Z',
107 account: {
108 name: 'root',
109 host: servers[0].host
110 },
111 isLocal,
112 publishedAt,
113 duration: 10,
114 tags: [ 'tag1p1', 'tag2p1' ],
115 privacy: VideoPrivacy.PUBLIC,
116 commentsEnabled: true,
117 downloadEnabled: true,
118 channel: {
119 displayName: 'my channel',
120 name: 'super_channel_name',
121 description: 'super channel',
122 isLocal
123 },
124 fixture: 'video_short1.webm',
125 files: [
126 {
127 resolution: 720,
128 size: 572456
129 }
130 ]
131 }
132
133 const { data } = await server.videos.list()
134 expect(data).to.be.an('array')
135 expect(data.length).to.equal(1)
136 const video = data[0]
137
138 await completeVideoCheck({ server, originServer: servers[0], videoUUID: video.uuid, attributes: checkAttributes })
139 publishedAt = video.publishedAt as string
140
141 expect(video.channel.avatars).to.have.lengthOf(2)
142 expect(video.account.avatars).to.have.lengthOf(2)
143
144 for (const image of [ ...video.channel.avatars, ...video.account.avatars ]) {
145 expect(image.createdAt).to.exist
146 expect(image.updatedAt).to.exist
147 expect(image.width).to.be.above(20).and.below(1000)
148 expect(image.path).to.exist
149
150 await makeGetRequest({
151 url: server.url,
152 path: image.path,
153 expectedStatus: HttpStatusCode.OK_200
154 })
155 }
156 }
157 })
158
159 it('Should upload the video on server 2 and propagate on each server', async function () {
160 this.timeout(240000)
161
162 const user = {
163 username: 'user1',
164 password: 'super_password'
165 }
166 await servers[1].users.create({ username: user.username, password: user.password })
167 const userAccessToken = await servers[1].login.getAccessToken(user)
168
169 const attributes = {
170 name: 'my super name for server 2',
171 category: 4,
172 licence: 3,
173 language: 'de',
174 nsfw: true,
175 description: 'my super description for server 2',
176 support: 'my super support text for server 2',
177 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
178 fixture: 'video_short2.webm',
179 thumbnailfile: 'custom-thumbnail.jpg',
180 previewfile: 'custom-preview.jpg'
181 }
182 await servers[1].videos.upload({ token: userAccessToken, attributes, mode: 'resumable' })
183
184 // Transcoding
185 await waitJobs(servers)
186
187 // All servers should have this video
188 for (const server of servers) {
189 const isLocal = server.url === servers[1].url
190 const checkAttributes = {
191 name: 'my super name for server 2',
192 category: 4,
193 licence: 3,
194 language: 'de',
195 nsfw: true,
196 description: 'my super description for server 2',
197 support: 'my super support text for server 2',
198 account: {
199 name: 'user1',
200 host: servers[1].host
201 },
202 isLocal,
203 commentsEnabled: true,
204 downloadEnabled: true,
205 duration: 5,
206 tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
207 privacy: VideoPrivacy.PUBLIC,
208 channel: {
209 displayName: 'Main user1 channel',
210 name: 'user1_channel',
211 description: 'super channel',
212 isLocal
213 },
214 fixture: 'video_short2.webm',
215 files: [
216 {
217 resolution: 240,
218 size: 270000
219 },
220 {
221 resolution: 360,
222 size: 359000
223 },
224 {
225 resolution: 480,
226 size: 465000
227 },
228 {
229 resolution: 720,
230 size: 750000
231 }
232 ],
233 thumbnailfile: 'custom-thumbnail',
234 previewfile: 'custom-preview'
235 }
236
237 const { data } = await server.videos.list()
238 expect(data).to.be.an('array')
239 expect(data.length).to.equal(2)
240 const video = data[1]
241
242 await completeVideoCheck({ server, originServer: servers[1], videoUUID: video.uuid, attributes: checkAttributes })
243 }
244 })
245
246 it('Should upload two videos on server 3 and propagate on each server', async function () {
247 this.timeout(45000)
248
249 {
250 const attributes = {
251 name: 'my super name for server 3',
252 category: 6,
253 licence: 5,
254 language: 'de',
255 nsfw: true,
256 description: 'my super description for server 3',
257 support: 'my super support text for server 3',
258 tags: [ 'tag1p3' ],
259 fixture: 'video_short3.webm'
260 }
261 await servers[2].videos.upload({ attributes })
262 }
263
264 {
265 const attributes = {
266 name: 'my super name for server 3-2',
267 category: 7,
268 licence: 6,
269 language: 'ko',
270 nsfw: false,
271 description: 'my super description for server 3-2',
272 support: 'my super support text for server 3-2',
273 tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
274 fixture: 'video_short.webm'
275 }
276 await servers[2].videos.upload({ attributes })
277 }
278
279 await waitJobs(servers)
280
281 // All servers should have this video
282 for (const server of servers) {
283 const isLocal = server.url === servers[2].url
284 const { data } = await server.videos.list()
285
286 expect(data).to.be.an('array')
287 expect(data.length).to.equal(4)
288
289 // We not sure about the order of the two last uploads
290 let video1 = null
291 let video2 = null
292 if (data[2].name === 'my super name for server 3') {
293 video1 = data[2]
294 video2 = data[3]
295 } else {
296 video1 = data[3]
297 video2 = data[2]
298 }
299
300 const checkAttributesVideo1 = {
301 name: 'my super name for server 3',
302 category: 6,
303 licence: 5,
304 language: 'de',
305 nsfw: true,
306 description: 'my super description for server 3',
307 support: 'my super support text for server 3',
308 account: {
309 name: 'root',
310 host: servers[2].host
311 },
312 isLocal,
313 duration: 5,
314 commentsEnabled: true,
315 downloadEnabled: true,
316 tags: [ 'tag1p3' ],
317 privacy: VideoPrivacy.PUBLIC,
318 channel: {
319 displayName: 'Main root channel',
320 name: 'root_channel',
321 description: '',
322 isLocal
323 },
324 fixture: 'video_short3.webm',
325 files: [
326 {
327 resolution: 720,
328 size: 292677
329 }
330 ]
331 }
332 await completeVideoCheck({ server, originServer: servers[2], videoUUID: video1.uuid, attributes: checkAttributesVideo1 })
333
334 const checkAttributesVideo2 = {
335 name: 'my super name for server 3-2',
336 category: 7,
337 licence: 6,
338 language: 'ko',
339 nsfw: false,
340 description: 'my super description for server 3-2',
341 support: 'my super support text for server 3-2',
342 account: {
343 name: 'root',
344 host: servers[2].host
345 },
346 commentsEnabled: true,
347 downloadEnabled: true,
348 isLocal,
349 duration: 5,
350 tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
351 privacy: VideoPrivacy.PUBLIC,
352 channel: {
353 displayName: 'Main root channel',
354 name: 'root_channel',
355 description: '',
356 isLocal
357 },
358 fixture: 'video_short.webm',
359 files: [
360 {
361 resolution: 720,
362 size: 218910
363 }
364 ]
365 }
366 await completeVideoCheck({ server, originServer: servers[2], videoUUID: video2.uuid, attributes: checkAttributesVideo2 })
367 }
368 })
369 })
370
371 describe('It should list local videos', function () {
372 it('Should list only local videos on server 1', async function () {
373 const { data, total } = await servers[0].videos.list({ isLocal: true })
374
375 expect(total).to.equal(1)
376 expect(data).to.be.an('array')
377 expect(data.length).to.equal(1)
378 expect(data[0].name).to.equal('my super name for server 1')
379 })
380
381 it('Should list only local videos on server 2', async function () {
382 const { data, total } = await servers[1].videos.list({ isLocal: true })
383
384 expect(total).to.equal(1)
385 expect(data).to.be.an('array')
386 expect(data.length).to.equal(1)
387 expect(data[0].name).to.equal('my super name for server 2')
388 })
389
390 it('Should list only local videos on server 3', async function () {
391 const { data, total } = await servers[2].videos.list({ isLocal: true })
392
393 expect(total).to.equal(2)
394 expect(data).to.be.an('array')
395 expect(data.length).to.equal(2)
396 expect(data[0].name).to.equal('my super name for server 3')
397 expect(data[1].name).to.equal('my super name for server 3-2')
398 })
399 })
400
401 describe('Should seed the uploaded video', function () {
402
403 it('Should add the file 1 by asking server 3', async function () {
404 this.retries(2)
405 this.timeout(30000)
406
407 const { data } = await servers[2].videos.list()
408
409 const video = data[0]
410 toRemove.push(data[2])
411 toRemove.push(data[3])
412
413 const videoDetails = await servers[2].videos.get({ id: video.id })
414
415 await checkWebTorrentWorks(videoDetails.files[0].magnetUri)
416 })
417
418 it('Should add the file 2 by asking server 1', async function () {
419 this.retries(2)
420 this.timeout(30000)
421
422 const { data } = await servers[0].videos.list()
423
424 const video = data[1]
425 const videoDetails = await servers[0].videos.get({ id: video.id })
426
427 await checkWebTorrentWorks(videoDetails.files[0].magnetUri)
428 })
429
430 it('Should add the file 3 by asking server 2', async function () {
431 this.retries(2)
432 this.timeout(30000)
433
434 const { data } = await servers[1].videos.list()
435
436 const video = data[2]
437 const videoDetails = await servers[1].videos.get({ id: video.id })
438
439 await checkWebTorrentWorks(videoDetails.files[0].magnetUri)
440 })
441
442 it('Should add the file 3-2 by asking server 1', async function () {
443 this.retries(2)
444 this.timeout(30000)
445
446 const { data } = await servers[0].videos.list()
447
448 const video = data[3]
449 const videoDetails = await servers[0].videos.get({ id: video.id })
450
451 await checkWebTorrentWorks(videoDetails.files[0].magnetUri)
452 })
453
454 it('Should add the file 2 in 360p by asking server 1', async function () {
455 this.retries(2)
456 this.timeout(30000)
457
458 const { data } = await servers[0].videos.list()
459
460 const video = data.find(v => v.name === 'my super name for server 2')
461 const videoDetails = await servers[0].videos.get({ id: video.id })
462
463 const file = videoDetails.files.find(f => f.resolution.id === 360)
464 expect(file).not.to.be.undefined
465
466 await checkWebTorrentWorks(file.magnetUri)
467 })
468 })
469
470 describe('Should update video views, likes and dislikes', function () {
471 let localVideosServer3 = []
472 let remoteVideosServer1 = []
473 let remoteVideosServer2 = []
474 let remoteVideosServer3 = []
475
476 before(async function () {
477 {
478 const { data } = await servers[0].videos.list()
479 remoteVideosServer1 = data.filter(video => video.isLocal === false).map(video => video.uuid)
480 }
481
482 {
483 const { data } = await servers[1].videos.list()
484 remoteVideosServer2 = data.filter(video => video.isLocal === false).map(video => video.uuid)
485 }
486
487 {
488 const { data } = await servers[2].videos.list()
489 localVideosServer3 = data.filter(video => video.isLocal === true).map(video => video.uuid)
490 remoteVideosServer3 = data.filter(video => video.isLocal === false).map(video => video.uuid)
491 }
492 })
493
494 it('Should view multiple videos on owned servers', async function () {
495 this.timeout(30000)
496
497 await servers[2].views.simulateView({ id: localVideosServer3[0] })
498 await wait(1000)
499
500 await servers[2].views.simulateView({ id: localVideosServer3[0] })
501 await servers[2].views.simulateView({ id: localVideosServer3[1] })
502
503 await wait(1000)
504
505 await servers[2].views.simulateView({ id: localVideosServer3[0] })
506 await servers[2].views.simulateView({ id: localVideosServer3[0] })
507
508 await waitJobs(servers)
509
510 for (const server of servers) {
511 await server.debug.sendCommand({ body: { command: 'process-video-views-buffer' } })
512 }
513
514 await waitJobs(servers)
515
516 for (const server of servers) {
517 const { data } = await server.videos.list()
518
519 const video0 = data.find(v => v.uuid === localVideosServer3[0])
520 const video1 = data.find(v => v.uuid === localVideosServer3[1])
521
522 expect(video0.views).to.equal(3)
523 expect(video1.views).to.equal(1)
524 }
525 })
526
527 it('Should view multiple videos on each servers', async function () {
528 this.timeout(45000)
529
530 const tasks: Promise<any>[] = []
531 tasks.push(servers[0].views.simulateView({ id: remoteVideosServer1[0] }))
532 tasks.push(servers[1].views.simulateView({ id: remoteVideosServer2[0] }))
533 tasks.push(servers[1].views.simulateView({ id: remoteVideosServer2[0] }))
534 tasks.push(servers[2].views.simulateView({ id: remoteVideosServer3[0] }))
535 tasks.push(servers[2].views.simulateView({ id: remoteVideosServer3[1] }))
536 tasks.push(servers[2].views.simulateView({ id: remoteVideosServer3[1] }))
537 tasks.push(servers[2].views.simulateView({ id: remoteVideosServer3[1] }))
538 tasks.push(servers[2].views.simulateView({ id: localVideosServer3[1] }))
539 tasks.push(servers[2].views.simulateView({ id: localVideosServer3[1] }))
540 tasks.push(servers[2].views.simulateView({ id: localVideosServer3[1] }))
541
542 await Promise.all(tasks)
543
544 await waitJobs(servers)
545
546 for (const server of servers) {
547 await server.debug.sendCommand({ body: { command: 'process-video-views-buffer' } })
548 }
549
550 await waitJobs(servers)
551
552 let baseVideos = null
553
554 for (const server of servers) {
555 const { data } = await server.videos.list()
556
557 // Initialize base videos for future comparisons
558 if (baseVideos === null) {
559 baseVideos = data
560 continue
561 }
562
563 for (const baseVideo of baseVideos) {
564 const sameVideo = data.find(video => video.name === baseVideo.name)
565 expect(baseVideo.views).to.equal(sameVideo.views)
566 }
567 }
568 })
569
570 it('Should like and dislikes videos on different services', async function () {
571 this.timeout(50000)
572
573 await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'like' })
574 await wait(500)
575 await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'dislike' })
576 await wait(500)
577 await servers[0].videos.rate({ id: remoteVideosServer1[0], rating: 'like' })
578 await servers[2].videos.rate({ id: localVideosServer3[1], rating: 'like' })
579 await wait(500)
580 await servers[2].videos.rate({ id: localVideosServer3[1], rating: 'dislike' })
581 await servers[2].videos.rate({ id: remoteVideosServer3[1], rating: 'dislike' })
582 await wait(500)
583 await servers[2].videos.rate({ id: remoteVideosServer3[0], rating: 'like' })
584
585 await waitJobs(servers)
586 await wait(5000)
587 await waitJobs(servers)
588
589 let baseVideos = null
590 for (const server of servers) {
591 const { data } = await server.videos.list()
592
593 // Initialize base videos for future comparisons
594 if (baseVideos === null) {
595 baseVideos = data
596 continue
597 }
598
599 for (const baseVideo of baseVideos) {
600 const sameVideo = data.find(video => video.name === baseVideo.name)
601 expect(baseVideo.likes).to.equal(sameVideo.likes, `Likes of ${sameVideo.uuid} do not correspond`)
602 expect(baseVideo.dislikes).to.equal(sameVideo.dislikes, `Dislikes of ${sameVideo.uuid} do not correspond`)
603 }
604 }
605 })
606 })
607
608 describe('Should manipulate these videos', function () {
609 let updatedAtMin: Date
610
611 it('Should update video 3', async function () {
612 this.timeout(30000)
613
614 const attributes = {
615 name: 'my super video updated',
616 category: 10,
617 licence: 7,
618 language: 'fr',
619 nsfw: true,
620 description: 'my super description updated',
621 support: 'my super support text updated',
622 tags: [ 'tag_up_1', 'tag_up_2' ],
623 thumbnailfile: 'custom-thumbnail.jpg',
624 originallyPublishedAt: '2019-02-11T13:38:14.449Z',
625 previewfile: 'custom-preview.jpg'
626 }
627
628 updatedAtMin = new Date()
629 await servers[2].videos.update({ id: toRemove[0].id, attributes })
630
631 await waitJobs(servers)
632 })
633
634 it('Should have the video 3 updated on each server', async function () {
635 this.timeout(30000)
636
637 for (const server of servers) {
638 const { data } = await server.videos.list()
639
640 const videoUpdated = data.find(video => video.name === 'my super video updated')
641 expect(!!videoUpdated).to.be.true
642
643 expect(new Date(videoUpdated.updatedAt)).to.be.greaterThan(updatedAtMin)
644
645 const isLocal = server.url === servers[2].url
646 const checkAttributes = {
647 name: 'my super video updated',
648 category: 10,
649 licence: 7,
650 language: 'fr',
651 nsfw: true,
652 description: 'my super description updated',
653 support: 'my super support text updated',
654 originallyPublishedAt: '2019-02-11T13:38:14.449Z',
655 account: {
656 name: 'root',
657 host: servers[2].host
658 },
659 isLocal,
660 duration: 5,
661 commentsEnabled: true,
662 downloadEnabled: true,
663 tags: [ 'tag_up_1', 'tag_up_2' ],
664 privacy: VideoPrivacy.PUBLIC,
665 channel: {
666 displayName: 'Main root channel',
667 name: 'root_channel',
668 description: '',
669 isLocal
670 },
671 fixture: 'video_short3.webm',
672 files: [
673 {
674 resolution: 720,
675 size: 292677
676 }
677 ],
678 thumbnailfile: 'custom-thumbnail',
679 previewfile: 'custom-preview'
680 }
681 await completeVideoCheck({ server, originServer: servers[2], videoUUID: videoUpdated.uuid, attributes: checkAttributes })
682 }
683 })
684
685 it('Should only update thumbnail and update updatedAt attribute', async function () {
686 this.timeout(30000)
687
688 const attributes = {
689 thumbnailfile: 'custom-thumbnail.jpg'
690 }
691
692 updatedAtMin = new Date()
693 await servers[2].videos.update({ id: toRemove[0].id, attributes })
694
695 await waitJobs(servers)
696
697 for (const server of servers) {
698 const { data } = await server.videos.list()
699
700 const videoUpdated = data.find(video => video.name === 'my super video updated')
701 expect(new Date(videoUpdated.updatedAt)).to.be.greaterThan(updatedAtMin)
702 }
703 })
704
705 it('Should remove the videos 3 and 3-2 by asking server 3 and correctly delete files', async function () {
706 this.timeout(30000)
707
708 for (const id of [ toRemove[0].id, toRemove[1].id ]) {
709 await saveVideoInServers(servers, id)
710
711 await servers[2].videos.remove({ id })
712
713 await waitJobs(servers)
714
715 for (const server of servers) {
716 await checkVideoFilesWereRemoved({ server, video: server.store.videoDetails })
717 }
718 }
719 })
720
721 it('Should have videos 1 and 3 on each server', async function () {
722 for (const server of servers) {
723 const { data } = await server.videos.list()
724
725 expect(data).to.be.an('array')
726 expect(data.length).to.equal(2)
727 expect(data[0].name).not.to.equal(data[1].name)
728 expect(data[0].name).not.to.equal(toRemove[0].name)
729 expect(data[1].name).not.to.equal(toRemove[0].name)
730 expect(data[0].name).not.to.equal(toRemove[1].name)
731 expect(data[1].name).not.to.equal(toRemove[1].name)
732
733 videoUUID = data.find(video => video.name === 'my super name for server 1').uuid
734 }
735 })
736
737 it('Should get the same video by UUID on each server', async function () {
738 let baseVideo = null
739 for (const server of servers) {
740 const video = await server.videos.get({ id: videoUUID })
741
742 if (baseVideo === null) {
743 baseVideo = video
744 continue
745 }
746
747 expect(baseVideo.name).to.equal(video.name)
748 expect(baseVideo.uuid).to.equal(video.uuid)
749 expect(baseVideo.category.id).to.equal(video.category.id)
750 expect(baseVideo.language.id).to.equal(video.language.id)
751 expect(baseVideo.licence.id).to.equal(video.licence.id)
752 expect(baseVideo.nsfw).to.equal(video.nsfw)
753 expect(baseVideo.account.name).to.equal(video.account.name)
754 expect(baseVideo.account.displayName).to.equal(video.account.displayName)
755 expect(baseVideo.account.url).to.equal(video.account.url)
756 expect(baseVideo.account.host).to.equal(video.account.host)
757 expect(baseVideo.tags).to.deep.equal(video.tags)
758 }
759 })
760
761 it('Should get the preview from each server', async function () {
762 for (const server of servers) {
763 const video = await server.videos.get({ id: videoUUID })
764
765 await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath)
766 }
767 })
768 })
769
770 describe('Should comment these videos', function () {
771 let childOfFirstChild: VideoCommentThreadTree
772
773 it('Should add comment (threads and replies)', async function () {
774 this.timeout(25000)
775
776 {
777 const text = 'my super first comment'
778 await servers[0].comments.createThread({ videoId: videoUUID, text })
779 }
780
781 {
782 const text = 'my super second comment'
783 await servers[2].comments.createThread({ videoId: videoUUID, text })
784 }
785
786 await waitJobs(servers)
787
788 {
789 const threadId = await servers[1].comments.findCommentId({ videoId: videoUUID, text: 'my super first comment' })
790
791 const text = 'my super answer to thread 1'
792 await servers[1].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text })
793 }
794
795 await waitJobs(servers)
796
797 {
798 const threadId = await servers[2].comments.findCommentId({ videoId: videoUUID, text: 'my super first comment' })
799
800 const body = await servers[2].comments.getThread({ videoId: videoUUID, threadId })
801 const childCommentId = body.children[0].comment.id
802
803 const text3 = 'my second answer to thread 1'
804 await servers[2].comments.addReply({ videoId: videoUUID, toCommentId: threadId, text: text3 })
805
806 const text2 = 'my super answer to answer of thread 1'
807 await servers[2].comments.addReply({ videoId: videoUUID, toCommentId: childCommentId, text: text2 })
808 }
809
810 await waitJobs(servers)
811 })
812
813 it('Should have these threads', async function () {
814 for (const server of servers) {
815 const body = await server.comments.listThreads({ videoId: videoUUID })
816
817 expect(body.total).to.equal(2)
818 expect(body.data).to.be.an('array')
819 expect(body.data).to.have.lengthOf(2)
820
821 {
822 const comment = body.data.find(c => c.text === 'my super first comment')
823 expect(comment).to.not.be.undefined
824 expect(comment.inReplyToCommentId).to.be.null
825 expect(comment.account.name).to.equal('root')
826 expect(comment.account.host).to.equal(servers[0].host)
827 expect(comment.totalReplies).to.equal(3)
828 expect(dateIsValid(comment.createdAt as string)).to.be.true
829 expect(dateIsValid(comment.updatedAt as string)).to.be.true
830 }
831
832 {
833 const comment = body.data.find(c => c.text === 'my super second comment')
834 expect(comment).to.not.be.undefined
835 expect(comment.inReplyToCommentId).to.be.null
836 expect(comment.account.name).to.equal('root')
837 expect(comment.account.host).to.equal(servers[2].host)
838 expect(comment.totalReplies).to.equal(0)
839 expect(dateIsValid(comment.createdAt as string)).to.be.true
840 expect(dateIsValid(comment.updatedAt as string)).to.be.true
841 }
842 }
843 })
844
845 it('Should have these comments', async function () {
846 for (const server of servers) {
847 const body = await server.comments.listThreads({ videoId: videoUUID })
848 const threadId = body.data.find(c => c.text === 'my super first comment').id
849
850 const tree = await server.comments.getThread({ videoId: videoUUID, threadId })
851
852 expect(tree.comment.text).equal('my super first comment')
853 expect(tree.comment.account.name).equal('root')
854 expect(tree.comment.account.host).equal(servers[0].host)
855 expect(tree.children).to.have.lengthOf(2)
856
857 const firstChild = tree.children[0]
858 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
859 expect(firstChild.comment.account.name).equal('root')
860 expect(firstChild.comment.account.host).equal(servers[1].host)
861 expect(firstChild.children).to.have.lengthOf(1)
862
863 childOfFirstChild = firstChild.children[0]
864 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
865 expect(childOfFirstChild.comment.account.name).equal('root')
866 expect(childOfFirstChild.comment.account.host).equal(servers[2].host)
867 expect(childOfFirstChild.children).to.have.lengthOf(0)
868
869 const secondChild = tree.children[1]
870 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
871 expect(secondChild.comment.account.name).equal('root')
872 expect(secondChild.comment.account.host).equal(servers[2].host)
873 expect(secondChild.children).to.have.lengthOf(0)
874 }
875 })
876
877 it('Should delete a reply', async function () {
878 this.timeout(30000)
879
880 await servers[2].comments.delete({ videoId: videoUUID, commentId: childOfFirstChild.comment.id })
881
882 await waitJobs(servers)
883 })
884
885 it('Should have this comment marked as deleted', async function () {
886 for (const server of servers) {
887 const { data } = await server.comments.listThreads({ videoId: videoUUID })
888 const threadId = data.find(c => c.text === 'my super first comment').id
889
890 const tree = await server.comments.getThread({ videoId: videoUUID, threadId })
891 expect(tree.comment.text).equal('my super first comment')
892
893 const firstChild = tree.children[0]
894 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
895 expect(firstChild.children).to.have.lengthOf(1)
896
897 const deletedComment = firstChild.children[0].comment
898 expect(deletedComment.isDeleted).to.be.true
899 expect(deletedComment.deletedAt).to.not.be.null
900 expect(deletedComment.account).to.be.null
901 expect(deletedComment.text).to.equal('')
902
903 const secondChild = tree.children[1]
904 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
905 }
906 })
907
908 it('Should delete the thread comments', async function () {
909 this.timeout(30000)
910
911 const { data } = await servers[0].comments.listThreads({ videoId: videoUUID })
912 const commentId = data.find(c => c.text === 'my super first comment').id
913 await servers[0].comments.delete({ videoId: videoUUID, commentId })
914
915 await waitJobs(servers)
916 })
917
918 it('Should have the threads marked as deleted on other servers too', async function () {
919 for (const server of servers) {
920 const body = await server.comments.listThreads({ videoId: videoUUID })
921
922 expect(body.total).to.equal(2)
923 expect(body.data).to.be.an('array')
924 expect(body.data).to.have.lengthOf(2)
925
926 {
927 const comment = body.data[0]
928 expect(comment).to.not.be.undefined
929 expect(comment.inReplyToCommentId).to.be.null
930 expect(comment.account.name).to.equal('root')
931 expect(comment.account.host).to.equal(servers[2].host)
932 expect(comment.totalReplies).to.equal(0)
933 expect(dateIsValid(comment.createdAt as string)).to.be.true
934 expect(dateIsValid(comment.updatedAt as string)).to.be.true
935 }
936
937 {
938 const deletedComment = body.data[1]
939 expect(deletedComment).to.not.be.undefined
940 expect(deletedComment.isDeleted).to.be.true
941 expect(deletedComment.deletedAt).to.not.be.null
942 expect(deletedComment.text).to.equal('')
943 expect(deletedComment.inReplyToCommentId).to.be.null
944 expect(deletedComment.account).to.be.null
945 expect(deletedComment.totalReplies).to.equal(2)
946 expect(dateIsValid(deletedComment.createdAt as string)).to.be.true
947 expect(dateIsValid(deletedComment.updatedAt as string)).to.be.true
948 expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true
949 }
950 }
951 })
952
953 it('Should delete a remote thread by the origin server', async function () {
954 this.timeout(5000)
955
956 const { data } = await servers[0].comments.listThreads({ videoId: videoUUID })
957 const commentId = data.find(c => c.text === 'my super second comment').id
958 await servers[0].comments.delete({ videoId: videoUUID, commentId })
959
960 await waitJobs(servers)
961 })
962
963 it('Should have the threads marked as deleted on other servers too', async function () {
964 for (const server of servers) {
965 const body = await server.comments.listThreads({ videoId: videoUUID })
966
967 expect(body.total).to.equal(2)
968 expect(body.data).to.have.lengthOf(2)
969
970 {
971 const comment = body.data[0]
972 expect(comment.text).to.equal('')
973 expect(comment.isDeleted).to.be.true
974 expect(comment.createdAt).to.not.be.null
975 expect(comment.deletedAt).to.not.be.null
976 expect(comment.account).to.be.null
977 expect(comment.totalReplies).to.equal(0)
978 }
979
980 {
981 const comment = body.data[1]
982 expect(comment.text).to.equal('')
983 expect(comment.isDeleted).to.be.true
984 expect(comment.createdAt).to.not.be.null
985 expect(comment.deletedAt).to.not.be.null
986 expect(comment.account).to.be.null
987 expect(comment.totalReplies).to.equal(2)
988 }
989 }
990 })
991
992 it('Should disable comments and download', async function () {
993 this.timeout(20000)
994
995 const attributes = {
996 commentsEnabled: false,
997 downloadEnabled: false
998 }
999
1000 await servers[0].videos.update({ id: videoUUID, attributes })
1001
1002 await waitJobs(servers)
1003
1004 for (const server of servers) {
1005 const video = await server.videos.get({ id: videoUUID })
1006 expect(video.commentsEnabled).to.be.false
1007 expect(video.downloadEnabled).to.be.false
1008
1009 const text = 'my super forbidden comment'
1010 await server.comments.createThread({ videoId: videoUUID, text, expectedStatus: HttpStatusCode.CONFLICT_409 })
1011 }
1012 })
1013 })
1014
1015 describe('With minimum parameters', function () {
1016 it('Should upload and propagate the video', async function () {
1017 this.timeout(120000)
1018
1019 const path = '/api/v1/videos/upload'
1020
1021 const req = request(servers[1].url)
1022 .post(path)
1023 .set('Accept', 'application/json')
1024 .set('Authorization', 'Bearer ' + servers[1].accessToken)
1025 .field('name', 'minimum parameters')
1026 .field('privacy', '1')
1027 .field('channelId', '1')
1028
1029 await req.attach('videofile', buildAbsoluteFixturePath('video_short.webm'))
1030 .expect(HttpStatusCode.OK_200)
1031
1032 await waitJobs(servers)
1033
1034 for (const server of servers) {
1035 const { data } = await server.videos.list()
1036 const video = data.find(v => v.name === 'minimum parameters')
1037
1038 const isLocal = server.url === servers[1].url
1039 const checkAttributes = {
1040 name: 'minimum parameters',
1041 category: null,
1042 licence: null,
1043 language: null,
1044 nsfw: false,
1045 description: null,
1046 support: null,
1047 account: {
1048 name: 'root',
1049 host: servers[1].host
1050 },
1051 isLocal,
1052 duration: 5,
1053 commentsEnabled: true,
1054 downloadEnabled: true,
1055 tags: [],
1056 privacy: VideoPrivacy.PUBLIC,
1057 channel: {
1058 displayName: 'Main root channel',
1059 name: 'root_channel',
1060 description: '',
1061 isLocal
1062 },
1063 fixture: 'video_short.webm',
1064 files: [
1065 {
1066 resolution: 720,
1067 size: 61000
1068 },
1069 {
1070 resolution: 480,
1071 size: 40000
1072 },
1073 {
1074 resolution: 360,
1075 size: 32000
1076 },
1077 {
1078 resolution: 240,
1079 size: 23000
1080 }
1081 ]
1082 }
1083 await completeVideoCheck({ server, originServer: servers[1], videoUUID: video.uuid, attributes: checkAttributes })
1084 }
1085 })
1086 })
1087
1088 describe('TMP directory', function () {
1089 it('Should have an empty tmp directory', async function () {
1090 for (const server of servers) {
1091 await checkTmpIsEmpty(server)
1092 }
1093 })
1094 })
1095
1096 after(async function () {
1097 await cleanupTests(servers)
1098 })
1099})
diff --git a/server/tests/api/videos/resumable-upload.ts b/server/tests/api/videos/resumable-upload.ts
deleted file mode 100644
index cac1201e9..000000000
--- a/server/tests/api/videos/resumable-upload.ts
+++ /dev/null
@@ -1,310 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir, stat } from 'fs-extra'
5import { join } from 'path'
6import { buildAbsoluteFixturePath } from '@shared/core-utils'
7import { sha1 } from '@shared/extra-utils'
8import { HttpStatusCode, VideoPrivacy } from '@shared/models'
9import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands'
10
11// Most classic resumable upload tests are done in other test suites
12
13describe('Test resumable upload', function () {
14 const path = '/api/v1/videos/upload-resumable'
15 const defaultFixture = 'video_short.mp4'
16 let server: PeerTubeServer
17 let rootId: number
18 let userAccessToken: string
19 let userChannelId: number
20
21 async function buildSize (fixture: string, size?: number) {
22 if (size !== undefined) return size
23
24 const baseFixture = buildAbsoluteFixturePath(fixture)
25 return (await stat(baseFixture)).size
26 }
27
28 async function prepareUpload (options: {
29 channelId?: number
30 token?: string
31 size?: number
32 originalName?: string
33 lastModified?: number
34 } = {}) {
35 const { token, originalName, lastModified } = options
36
37 const size = await buildSize(defaultFixture, options.size)
38
39 const attributes = {
40 name: 'video',
41 channelId: options.channelId ?? server.store.channel.id,
42 privacy: VideoPrivacy.PUBLIC,
43 fixture: defaultFixture
44 }
45
46 const mimetype = 'video/mp4'
47
48 const res = await server.videos.prepareResumableUpload({ path, token, attributes, size, mimetype, originalName, lastModified })
49
50 return res.header['location'].split('?')[1]
51 }
52
53 async function sendChunks (options: {
54 token?: string
55 pathUploadId: string
56 size?: number
57 expectedStatus?: HttpStatusCode
58 contentLength?: number
59 contentRange?: string
60 contentRangeBuilder?: (start: number, chunk: any) => string
61 digestBuilder?: (chunk: any) => string
62 }) {
63 const { token, pathUploadId, expectedStatus, contentLength, contentRangeBuilder, digestBuilder } = options
64
65 const size = await buildSize(defaultFixture, options.size)
66 const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
67
68 return server.videos.sendResumableChunks({
69 token,
70 path,
71 pathUploadId,
72 videoFilePath: absoluteFilePath,
73 size,
74 contentLength,
75 contentRangeBuilder,
76 digestBuilder,
77 expectedStatus
78 })
79 }
80
81 async function checkFileSize (uploadIdArg: string, expectedSize: number | null) {
82 const uploadId = uploadIdArg.replace(/^upload_id=/, '')
83
84 const subPath = join('tmp', 'resumable-uploads', `${rootId}-${uploadId}.mp4`)
85 const filePath = server.servers.buildDirectory(subPath)
86 const exists = await pathExists(filePath)
87
88 if (expectedSize === null) {
89 expect(exists).to.be.false
90 return
91 }
92
93 expect(exists).to.be.true
94
95 expect((await stat(filePath)).size).to.equal(expectedSize)
96 }
97
98 async function countResumableUploads (wait?: number) {
99 const subPath = join('tmp', 'resumable-uploads')
100 const filePath = server.servers.buildDirectory(subPath)
101 await new Promise(resolve => setTimeout(resolve, wait))
102 const files = await readdir(filePath)
103 return files.length
104 }
105
106 before(async function () {
107 this.timeout(30000)
108
109 server = await createSingleServer(1)
110 await setAccessTokensToServers([ server ])
111 await setDefaultVideoChannel([ server ])
112
113 const body = await server.users.getMyInfo()
114 rootId = body.id
115
116 {
117 userAccessToken = await server.users.generateUserAndToken('user1')
118 const { videoChannels } = await server.users.getMyInfo({ token: userAccessToken })
119 userChannelId = videoChannels[0].id
120 }
121
122 await server.users.update({ userId: rootId, videoQuota: 10_000_000 })
123 })
124
125 describe('Directory cleaning', function () {
126
127 it('Should correctly delete files after an upload', async function () {
128 const uploadId = await prepareUpload()
129 await sendChunks({ pathUploadId: uploadId })
130 await server.videos.endResumableUpload({ path, pathUploadId: uploadId })
131
132 expect(await countResumableUploads()).to.equal(0)
133 })
134
135 it('Should correctly delete corrupt files', async function () {
136 const uploadId = await prepareUpload({ size: 8 * 1024 })
137 await sendChunks({ pathUploadId: uploadId, size: 8 * 1024, expectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422 })
138
139 expect(await countResumableUploads(2000)).to.equal(0)
140 })
141
142 it('Should not delete files after an unfinished upload', async function () {
143 await prepareUpload()
144
145 expect(await countResumableUploads()).to.equal(2)
146 })
147
148 it('Should not delete recent uploads', async function () {
149 await server.debug.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } })
150
151 expect(await countResumableUploads()).to.equal(2)
152 })
153
154 it('Should delete old uploads', async function () {
155 await server.debug.sendCommand({ body: { command: 'remove-dandling-resumable-uploads' } })
156
157 expect(await countResumableUploads()).to.equal(0)
158 })
159 })
160
161 describe('Resumable upload and chunks', function () {
162
163 it('Should accept the same amount of chunks', async function () {
164 const uploadId = await prepareUpload()
165 await sendChunks({ pathUploadId: uploadId })
166
167 await checkFileSize(uploadId, null)
168 })
169
170 it('Should not accept more chunks than expected', async function () {
171 const uploadId = await prepareUpload({ size: 100 })
172
173 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 })
174 await checkFileSize(uploadId, 0)
175 })
176
177 it('Should not accept more chunks than expected with an invalid content length/content range', async function () {
178 const uploadId = await prepareUpload({ size: 1500 })
179
180 // Content length check can be different depending on the node version
181 try {
182 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409, contentLength: 1000 })
183 await checkFileSize(uploadId, 0)
184 } catch {
185 await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.BAD_REQUEST_400, contentLength: 1000 })
186 await checkFileSize(uploadId, 0)
187 }
188 })
189
190 it('Should not accept more chunks than expected with an invalid content length', async function () {
191 const uploadId = await prepareUpload({ size: 500 })
192
193 const size = 1000
194
195 // Content length check seems to have changed in v16
196 const expectedStatus = process.version.startsWith('v16')
197 ? HttpStatusCode.CONFLICT_409
198 : HttpStatusCode.BAD_REQUEST_400
199
200 const contentRangeBuilder = (start: number) => `bytes ${start}-${start + size - 1}/${size}`
201 await sendChunks({ pathUploadId: uploadId, expectedStatus, contentRangeBuilder, contentLength: size })
202 await checkFileSize(uploadId, 0)
203 })
204
205 it('Should be able to accept 2 PUT requests', async function () {
206 const uploadId = await prepareUpload()
207
208 const result1 = await sendChunks({ pathUploadId: uploadId })
209 const result2 = await sendChunks({ pathUploadId: uploadId })
210
211 expect(result1.body.video.uuid).to.exist
212 expect(result1.body.video.uuid).to.equal(result2.body.video.uuid)
213
214 expect(result1.headers['x-resumable-upload-cached']).to.not.exist
215 expect(result2.headers['x-resumable-upload-cached']).to.equal('true')
216
217 await checkFileSize(uploadId, null)
218 })
219
220 it('Should not have the same upload id with 2 different users', async function () {
221 const originalName = 'toto.mp4'
222 const lastModified = new Date().getTime()
223
224 const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
225 const uploadId2 = await prepareUpload({ originalName, lastModified, channelId: userChannelId, token: userAccessToken })
226
227 expect(uploadId1).to.not.equal(uploadId2)
228 })
229
230 it('Should have the same upload id with the same user', async function () {
231 const originalName = 'toto.mp4'
232 const lastModified = new Date().getTime()
233
234 const uploadId1 = await prepareUpload({ originalName, lastModified })
235 const uploadId2 = await prepareUpload({ originalName, lastModified })
236
237 expect(uploadId1).to.equal(uploadId2)
238 })
239
240 it('Should not cache a request with 2 different users', async function () {
241 const originalName = 'toto.mp4'
242 const lastModified = new Date().getTime()
243
244 const uploadId = await prepareUpload({ originalName, lastModified, token: server.accessToken })
245
246 await sendChunks({ pathUploadId: uploadId, token: server.accessToken })
247 await sendChunks({ pathUploadId: uploadId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
248 })
249
250 it('Should not cache a request after a delete', async function () {
251 const originalName = 'toto.mp4'
252 const lastModified = new Date().getTime()
253 const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
254
255 await sendChunks({ pathUploadId: uploadId1 })
256 await server.videos.endResumableUpload({ path, pathUploadId: uploadId1 })
257
258 const uploadId2 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
259 expect(uploadId1).to.equal(uploadId2)
260
261 const result2 = await sendChunks({ pathUploadId: uploadId1 })
262 expect(result2.headers['x-resumable-upload-cached']).to.not.exist
263 })
264
265 it('Should not cache after video deletion', async function () {
266 const originalName = 'toto.mp4'
267 const lastModified = new Date().getTime()
268
269 const uploadId1 = await prepareUpload({ originalName, lastModified })
270 const result1 = await sendChunks({ pathUploadId: uploadId1 })
271 await server.videos.remove({ id: result1.body.video.uuid })
272
273 const uploadId2 = await prepareUpload({ originalName, lastModified })
274 const result2 = await sendChunks({ pathUploadId: uploadId2 })
275 expect(result1.body.video.uuid).to.not.equal(result2.body.video.uuid)
276
277 expect(result2.headers['x-resumable-upload-cached']).to.not.exist
278
279 await checkFileSize(uploadId1, null)
280 await checkFileSize(uploadId2, null)
281 })
282
283 it('Should refuse an invalid digest', async function () {
284 const uploadId = await prepareUpload({ token: server.accessToken })
285
286 await sendChunks({
287 pathUploadId: uploadId,
288 token: server.accessToken,
289 digestBuilder: () => 'sha=' + 'a'.repeat(40),
290 expectedStatus: 460 as any
291 })
292 })
293
294 it('Should accept an appropriate digest', async function () {
295 const uploadId = await prepareUpload({ token: server.accessToken })
296
297 await sendChunks({
298 pathUploadId: uploadId,
299 token: server.accessToken,
300 digestBuilder: (chunk: Buffer) => {
301 return 'sha1=' + sha1(chunk, 'base64')
302 }
303 })
304 })
305 })
306
307 after(async function () {
308 await cleanupTests([ server ])
309 })
310})
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
deleted file mode 100644
index 66414aa5b..000000000
--- a/server/tests/api/videos/single-server.ts
+++ /dev/null
@@ -1,460 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkVideoFilesWereRemoved, completeVideoCheck, testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { wait } from '@shared/core-utils'
6import { Video, VideoPrivacy } from '@shared/models'
7import {
8 cleanupTests,
9 createSingleServer,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultAccountAvatar,
13 setDefaultChannelAvatar,
14 waitJobs
15} from '@shared/server-commands'
16
17describe('Test a single server', function () {
18
19 function runSuite (mode: 'legacy' | 'resumable') {
20 let server: PeerTubeServer = null
21 let videoId: number | string
22 let videoId2: string
23 let videoUUID = ''
24 let videosListBase: any[] = null
25
26 const getCheckAttributes = () => ({
27 name: 'my super name',
28 category: 2,
29 licence: 6,
30 language: 'zh',
31 nsfw: true,
32 description: 'my super description',
33 support: 'my super support text',
34 account: {
35 name: 'root',
36 host: server.host
37 },
38 isLocal: true,
39 duration: 5,
40 tags: [ 'tag1', 'tag2', 'tag3' ],
41 privacy: VideoPrivacy.PUBLIC,
42 commentsEnabled: true,
43 downloadEnabled: true,
44 channel: {
45 displayName: 'Main root channel',
46 name: 'root_channel',
47 description: '',
48 isLocal: true
49 },
50 fixture: 'video_short.webm',
51 files: [
52 {
53 resolution: 720,
54 size: 218910
55 }
56 ]
57 })
58
59 const updateCheckAttributes = () => ({
60 name: 'my super video updated',
61 category: 4,
62 licence: 2,
63 language: 'ar',
64 nsfw: false,
65 description: 'my super description updated',
66 support: 'my super support text updated',
67 account: {
68 name: 'root',
69 host: server.host
70 },
71 isLocal: true,
72 tags: [ 'tagup1', 'tagup2' ],
73 privacy: VideoPrivacy.PUBLIC,
74 duration: 5,
75 commentsEnabled: false,
76 downloadEnabled: false,
77 channel: {
78 name: 'root_channel',
79 displayName: 'Main root channel',
80 description: '',
81 isLocal: true
82 },
83 fixture: 'video_short3.webm',
84 files: [
85 {
86 resolution: 720,
87 size: 292677
88 }
89 ]
90 })
91
92 before(async function () {
93 this.timeout(30000)
94
95 server = await createSingleServer(1)
96
97 await setAccessTokensToServers([ server ])
98 await setDefaultChannelAvatar(server)
99 await setDefaultAccountAvatar(server)
100 })
101
102 it('Should list video categories', async function () {
103 const categories = await server.videos.getCategories()
104 expect(Object.keys(categories)).to.have.length.above(10)
105
106 expect(categories[11]).to.equal('News & Politics')
107 })
108
109 it('Should list video licences', async function () {
110 const licences = await server.videos.getLicences()
111 expect(Object.keys(licences)).to.have.length.above(5)
112
113 expect(licences[3]).to.equal('Attribution - No Derivatives')
114 })
115
116 it('Should list video languages', async function () {
117 const languages = await server.videos.getLanguages()
118 expect(Object.keys(languages)).to.have.length.above(5)
119
120 expect(languages['ru']).to.equal('Russian')
121 })
122
123 it('Should list video privacies', async function () {
124 const privacies = await server.videos.getPrivacies()
125 expect(Object.keys(privacies)).to.have.length.at.least(3)
126
127 expect(privacies[3]).to.equal('Private')
128 })
129
130 it('Should not have videos', async function () {
131 const { data, total } = await server.videos.list()
132
133 expect(total).to.equal(0)
134 expect(data).to.be.an('array')
135 expect(data.length).to.equal(0)
136 })
137
138 it('Should upload the video', async function () {
139 const attributes = {
140 name: 'my super name',
141 category: 2,
142 nsfw: true,
143 licence: 6,
144 tags: [ 'tag1', 'tag2', 'tag3' ]
145 }
146 const video = await server.videos.upload({ attributes, mode })
147 expect(video).to.not.be.undefined
148 expect(video.id).to.equal(1)
149 expect(video.uuid).to.have.length.above(5)
150
151 videoId = video.id
152 videoUUID = video.uuid
153 })
154
155 it('Should get and seed the uploaded video', async function () {
156 this.timeout(5000)
157
158 const { data, total } = await server.videos.list()
159
160 expect(total).to.equal(1)
161 expect(data).to.be.an('array')
162 expect(data.length).to.equal(1)
163
164 const video = data[0]
165 await completeVideoCheck({ server, originServer: server, videoUUID: video.uuid, attributes: getCheckAttributes() })
166 })
167
168 it('Should get the video by UUID', async function () {
169 this.timeout(5000)
170
171 const video = await server.videos.get({ id: videoUUID })
172 await completeVideoCheck({ server, originServer: server, videoUUID: video.uuid, attributes: getCheckAttributes() })
173 })
174
175 it('Should have the views updated', async function () {
176 this.timeout(20000)
177
178 await server.views.simulateView({ id: videoId })
179 await server.views.simulateView({ id: videoId })
180 await server.views.simulateView({ id: videoId })
181
182 await wait(1500)
183
184 await server.views.simulateView({ id: videoId })
185 await server.views.simulateView({ id: videoId })
186
187 await wait(1500)
188
189 await server.views.simulateView({ id: videoId })
190 await server.views.simulateView({ id: videoId })
191
192 await server.debug.sendCommand({ body: { command: 'process-video-views-buffer' } })
193
194 const video = await server.videos.get({ id: videoId })
195 expect(video.views).to.equal(3)
196 })
197
198 it('Should remove the video', async function () {
199 const video = await server.videos.get({ id: videoId })
200 await server.videos.remove({ id: videoId })
201
202 await checkVideoFilesWereRemoved({ video, server })
203 })
204
205 it('Should not have videos', async function () {
206 const { total, data } = await server.videos.list()
207
208 expect(total).to.equal(0)
209 expect(data).to.be.an('array')
210 expect(data).to.have.lengthOf(0)
211 })
212
213 it('Should upload 6 videos', async function () {
214 this.timeout(120000)
215
216 const videos = new Set([
217 'video_short.mp4', 'video_short.ogv', 'video_short.webm',
218 'video_short1.webm', 'video_short2.webm', 'video_short3.webm'
219 ])
220
221 for (const video of videos) {
222 const attributes = {
223 name: video + ' name',
224 description: video + ' description',
225 category: 2,
226 licence: 1,
227 language: 'en',
228 nsfw: true,
229 tags: [ 'tag1', 'tag2', 'tag3' ],
230 fixture: video
231 }
232
233 await server.videos.upload({ attributes, mode })
234 }
235 })
236
237 it('Should have the correct durations', async function () {
238 const { total, data } = await server.videos.list()
239
240 expect(total).to.equal(6)
241 expect(data).to.be.an('array')
242 expect(data).to.have.lengthOf(6)
243
244 const videosByName: { [ name: string ]: Video } = {}
245 data.forEach(v => { videosByName[v.name] = v })
246
247 expect(videosByName['video_short.mp4 name'].duration).to.equal(5)
248 expect(videosByName['video_short.ogv name'].duration).to.equal(5)
249 expect(videosByName['video_short.webm name'].duration).to.equal(5)
250 expect(videosByName['video_short1.webm name'].duration).to.equal(10)
251 expect(videosByName['video_short2.webm name'].duration).to.equal(5)
252 expect(videosByName['video_short3.webm name'].duration).to.equal(5)
253 })
254
255 it('Should have the correct thumbnails', async function () {
256 const { data } = await server.videos.list()
257
258 // For the next test
259 videosListBase = data
260
261 for (const video of data) {
262 const videoName = video.name.replace(' name', '')
263 await testImageGeneratedByFFmpeg(server.url, videoName, video.thumbnailPath)
264 }
265 })
266
267 it('Should list only the two first videos', async function () {
268 const { total, data } = await server.videos.list({ start: 0, count: 2, sort: 'name' })
269
270 expect(total).to.equal(6)
271 expect(data.length).to.equal(2)
272 expect(data[0].name).to.equal(videosListBase[0].name)
273 expect(data[1].name).to.equal(videosListBase[1].name)
274 })
275
276 it('Should list only the next three videos', async function () {
277 const { total, data } = await server.videos.list({ start: 2, count: 3, sort: 'name' })
278
279 expect(total).to.equal(6)
280 expect(data.length).to.equal(3)
281 expect(data[0].name).to.equal(videosListBase[2].name)
282 expect(data[1].name).to.equal(videosListBase[3].name)
283 expect(data[2].name).to.equal(videosListBase[4].name)
284 })
285
286 it('Should list the last video', async function () {
287 const { total, data } = await server.videos.list({ start: 5, count: 6, sort: 'name' })
288
289 expect(total).to.equal(6)
290 expect(data.length).to.equal(1)
291 expect(data[0].name).to.equal(videosListBase[5].name)
292 })
293
294 it('Should not have the total field', async function () {
295 const { total, data } = await server.videos.list({ start: 5, count: 6, sort: 'name', skipCount: true })
296
297 expect(total).to.not.exist
298 expect(data.length).to.equal(1)
299 expect(data[0].name).to.equal(videosListBase[5].name)
300 })
301
302 it('Should list and sort by name in descending order', async function () {
303 const { total, data } = await server.videos.list({ sort: '-name' })
304
305 expect(total).to.equal(6)
306 expect(data.length).to.equal(6)
307 expect(data[0].name).to.equal('video_short.webm name')
308 expect(data[1].name).to.equal('video_short.ogv name')
309 expect(data[2].name).to.equal('video_short.mp4 name')
310 expect(data[3].name).to.equal('video_short3.webm name')
311 expect(data[4].name).to.equal('video_short2.webm name')
312 expect(data[5].name).to.equal('video_short1.webm name')
313
314 videoId = data[3].uuid
315 videoId2 = data[5].uuid
316 })
317
318 it('Should list and sort by trending in descending order', async function () {
319 const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-trending' })
320
321 expect(total).to.equal(6)
322 expect(data.length).to.equal(2)
323 })
324
325 it('Should list and sort by hotness in descending order', async function () {
326 const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-hot' })
327
328 expect(total).to.equal(6)
329 expect(data.length).to.equal(2)
330 })
331
332 it('Should list and sort by best in descending order', async function () {
333 const { total, data } = await server.videos.list({ start: 0, count: 2, sort: '-best' })
334
335 expect(total).to.equal(6)
336 expect(data.length).to.equal(2)
337 })
338
339 it('Should update a video', async function () {
340 const attributes = {
341 name: 'my super video updated',
342 category: 4,
343 licence: 2,
344 language: 'ar',
345 nsfw: false,
346 description: 'my super description updated',
347 commentsEnabled: false,
348 downloadEnabled: false,
349 tags: [ 'tagup1', 'tagup2' ]
350 }
351 await server.videos.update({ id: videoId, attributes })
352 })
353
354 it('Should have the video updated', async function () {
355 this.timeout(60000)
356
357 await waitJobs([ server ])
358
359 const video = await server.videos.get({ id: videoId })
360
361 await completeVideoCheck({ server, originServer: server, videoUUID: video.uuid, attributes: updateCheckAttributes() })
362 })
363
364 it('Should update only the tags of a video', async function () {
365 const attributes = {
366 tags: [ 'supertag', 'tag1', 'tag2' ]
367 }
368 await server.videos.update({ id: videoId, attributes })
369
370 const video = await server.videos.get({ id: videoId })
371
372 await completeVideoCheck({
373 server,
374 originServer: server,
375 videoUUID: video.uuid,
376 attributes: Object.assign(updateCheckAttributes(), attributes)
377 })
378 })
379
380 it('Should update only the description of a video', async function () {
381 const attributes = {
382 description: 'hello everybody'
383 }
384 await server.videos.update({ id: videoId, attributes })
385
386 const video = await server.videos.get({ id: videoId })
387
388 await completeVideoCheck({
389 server,
390 originServer: server,
391 videoUUID: video.uuid,
392 attributes: Object.assign(updateCheckAttributes(), { tags: [ 'supertag', 'tag1', 'tag2' ] }, attributes)
393 })
394 })
395
396 it('Should like a video', async function () {
397 await server.videos.rate({ id: videoId, rating: 'like' })
398
399 const video = await server.videos.get({ id: videoId })
400
401 expect(video.likes).to.equal(1)
402 expect(video.dislikes).to.equal(0)
403 })
404
405 it('Should dislike the same video', async function () {
406 await server.videos.rate({ id: videoId, rating: 'dislike' })
407
408 const video = await server.videos.get({ id: videoId })
409
410 expect(video.likes).to.equal(0)
411 expect(video.dislikes).to.equal(1)
412 })
413
414 it('Should sort by originallyPublishedAt', async function () {
415 {
416 const now = new Date()
417 const attributes = { originallyPublishedAt: now.toISOString() }
418 await server.videos.update({ id: videoId, attributes })
419
420 const { data } = await server.videos.list({ sort: '-originallyPublishedAt' })
421 const names = data.map(v => v.name)
422
423 expect(names[0]).to.equal('my super video updated')
424 expect(names[1]).to.equal('video_short2.webm name')
425 expect(names[2]).to.equal('video_short1.webm name')
426 expect(names[3]).to.equal('video_short.webm name')
427 expect(names[4]).to.equal('video_short.ogv name')
428 expect(names[5]).to.equal('video_short.mp4 name')
429 }
430
431 {
432 const now = new Date()
433 const attributes = { originallyPublishedAt: now.toISOString() }
434 await server.videos.update({ id: videoId2, attributes })
435
436 const { data } = await server.videos.list({ sort: '-originallyPublishedAt' })
437 const names = data.map(v => v.name)
438
439 expect(names[0]).to.equal('video_short1.webm name')
440 expect(names[1]).to.equal('my super video updated')
441 expect(names[2]).to.equal('video_short2.webm name')
442 expect(names[3]).to.equal('video_short.webm name')
443 expect(names[4]).to.equal('video_short.ogv name')
444 expect(names[5]).to.equal('video_short.mp4 name')
445 }
446 })
447
448 after(async function () {
449 await cleanupTests([ server ])
450 })
451 }
452
453 describe('Legacy upload', function () {
454 runSuite('legacy')
455 })
456
457 describe('Resumable upload', function () {
458 runSuite('resumable')
459 })
460})
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts
deleted file mode 100644
index 0630c9d3a..000000000
--- a/server/tests/api/videos/video-captions.ts
+++ /dev/null
@@ -1,188 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkVideoFilesWereRemoved, testCaptionFile } from '@server/tests/shared'
5import { wait } from '@shared/core-utils'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
14
15describe('Test video captions', function () {
16 const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
17
18 let servers: PeerTubeServer[]
19 let videoUUID: string
20
21 before(async function () {
22 this.timeout(60000)
23
24 servers = await createMultipleServers(2)
25
26 await setAccessTokensToServers(servers)
27 await doubleFollow(servers[0], servers[1])
28
29 await waitJobs(servers)
30
31 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'my video name' } })
32 videoUUID = uuid
33
34 await waitJobs(servers)
35 })
36
37 it('Should list the captions and return an empty list', async function () {
38 for (const server of servers) {
39 const body = await server.captions.list({ videoId: videoUUID })
40 expect(body.total).to.equal(0)
41 expect(body.data).to.have.lengthOf(0)
42 }
43 })
44
45 it('Should create two new captions', async function () {
46 this.timeout(30000)
47
48 await servers[0].captions.add({
49 language: 'ar',
50 videoId: videoUUID,
51 fixture: 'subtitle-good1.vtt'
52 })
53
54 await servers[0].captions.add({
55 language: 'zh',
56 videoId: videoUUID,
57 fixture: 'subtitle-good2.vtt',
58 mimeType: 'application/octet-stream'
59 })
60
61 await waitJobs(servers)
62 })
63
64 it('Should list these uploaded captions', async function () {
65 for (const server of servers) {
66 const body = await server.captions.list({ videoId: videoUUID })
67 expect(body.total).to.equal(2)
68 expect(body.data).to.have.lengthOf(2)
69
70 const caption1 = body.data[0]
71 expect(caption1.language.id).to.equal('ar')
72 expect(caption1.language.label).to.equal('Arabic')
73 expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
74 await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.')
75
76 const caption2 = body.data[1]
77 expect(caption2.language.id).to.equal('zh')
78 expect(caption2.language.label).to.equal('Chinese')
79 expect(caption2.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$'))
80 await testCaptionFile(server.url, caption2.captionPath, 'Subtitle good 2.')
81 }
82 })
83
84 it('Should replace an existing caption', async function () {
85 this.timeout(30000)
86
87 await servers[0].captions.add({
88 language: 'ar',
89 videoId: videoUUID,
90 fixture: 'subtitle-good2.vtt'
91 })
92
93 await waitJobs(servers)
94 })
95
96 it('Should have this caption updated', async function () {
97 for (const server of servers) {
98 const body = await server.captions.list({ videoId: videoUUID })
99 expect(body.total).to.equal(2)
100 expect(body.data).to.have.lengthOf(2)
101
102 const caption1 = body.data[0]
103 expect(caption1.language.id).to.equal('ar')
104 expect(caption1.language.label).to.equal('Arabic')
105 expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
106 await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 2.')
107 }
108 })
109
110 it('Should replace an existing caption with a srt file and convert it', async function () {
111 this.timeout(30000)
112
113 await servers[0].captions.add({
114 language: 'ar',
115 videoId: videoUUID,
116 fixture: 'subtitle-good.srt'
117 })
118
119 await waitJobs(servers)
120
121 // Cache invalidation
122 await wait(3000)
123 })
124
125 it('Should have this caption updated and converted', async function () {
126 for (const server of servers) {
127 const body = await server.captions.list({ videoId: videoUUID })
128 expect(body.total).to.equal(2)
129 expect(body.data).to.have.lengthOf(2)
130
131 const caption1 = body.data[0]
132 expect(caption1.language.id).to.equal('ar')
133 expect(caption1.language.label).to.equal('Arabic')
134 expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
135
136 const expected = 'WEBVTT FILE\r\n' +
137 '\r\n' +
138 '1\r\n' +
139 '00:00:01.600 --> 00:00:04.200\r\n' +
140 'English (US)\r\n' +
141 '\r\n' +
142 '2\r\n' +
143 '00:00:05.900 --> 00:00:07.999\r\n' +
144 'This is a subtitle in American English\r\n' +
145 '\r\n' +
146 '3\r\n' +
147 '00:00:10.000 --> 00:00:14.000\r\n' +
148 'Adding subtitles is very easy to do\r\n'
149 await testCaptionFile(server.url, caption1.captionPath, expected)
150 }
151 })
152
153 it('Should remove one caption', async function () {
154 this.timeout(30000)
155
156 await servers[0].captions.delete({ videoId: videoUUID, language: 'ar' })
157
158 await waitJobs(servers)
159 })
160
161 it('Should only list the caption that was not deleted', async function () {
162 for (const server of servers) {
163 const body = await server.captions.list({ videoId: videoUUID })
164 expect(body.total).to.equal(1)
165 expect(body.data).to.have.lengthOf(1)
166
167 const caption = body.data[0]
168
169 expect(caption.language.id).to.equal('zh')
170 expect(caption.language.label).to.equal('Chinese')
171 expect(caption.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$'))
172 await testCaptionFile(server.url, caption.captionPath, 'Subtitle good 2.')
173 }
174 })
175
176 it('Should remove the video, and thus all video captions', async function () {
177 const video = await servers[0].videos.get({ id: videoUUID })
178 const { data: captions } = await servers[0].captions.list({ videoId: videoUUID })
179
180 await servers[0].videos.remove({ id: videoUUID })
181
182 await checkVideoFilesWereRemoved({ server: servers[0], video, captions })
183 })
184
185 after(async function () {
186 await cleanupTests(servers)
187 })
188})
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
deleted file mode 100644
index 99d774c2b..000000000
--- a/server/tests/api/videos/video-change-ownership.ts
+++ /dev/null
@@ -1,314 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import {
5 ChangeOwnershipCommand,
6 cleanupTests,
7 createMultipleServers,
8 createSingleServer,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 waitJobs
14} from '@shared/server-commands'
15import { HttpStatusCode, VideoPrivacy } from '@shared/models'
16
17describe('Test video change ownership - nominal', function () {
18 let servers: PeerTubeServer[] = []
19
20 const firstUser = 'first'
21 const secondUser = 'second'
22
23 let firstUserToken = ''
24 let firstUserChannelId: number
25
26 let secondUserToken = ''
27 let secondUserChannelId: number
28
29 let lastRequestId: number
30
31 let liveId: number
32
33 let command: ChangeOwnershipCommand
34
35 before(async function () {
36 this.timeout(50000)
37
38 servers = await createMultipleServers(2)
39 await setAccessTokensToServers(servers)
40 await setDefaultVideoChannel(servers)
41
42 await servers[0].config.updateCustomSubConfig({
43 newConfig: {
44 transcoding: {
45 enabled: false
46 },
47 live: {
48 enabled: true
49 }
50 }
51 })
52
53 firstUserToken = await servers[0].users.generateUserAndToken(firstUser)
54 secondUserToken = await servers[0].users.generateUserAndToken(secondUser)
55
56 {
57 const { videoChannels } = await servers[0].users.getMyInfo({ token: firstUserToken })
58 firstUserChannelId = videoChannels[0].id
59 }
60
61 {
62 const { videoChannels } = await servers[0].users.getMyInfo({ token: secondUserToken })
63 secondUserChannelId = videoChannels[0].id
64 }
65
66 {
67 const attributes = {
68 name: 'my super name',
69 description: 'my super description'
70 }
71 const { id } = await servers[0].videos.upload({ token: firstUserToken, attributes })
72
73 servers[0].store.videoCreated = await servers[0].videos.get({ id })
74 }
75
76 {
77 const attributes = { name: 'live', channelId: firstUserChannelId, privacy: VideoPrivacy.PUBLIC }
78 const video = await servers[0].live.create({ token: firstUserToken, fields: attributes })
79
80 liveId = video.id
81 }
82
83 command = servers[0].changeOwnership
84
85 await doubleFollow(servers[0], servers[1])
86 })
87
88 it('Should not have video change ownership', async function () {
89 {
90 const body = await command.list({ token: firstUserToken })
91
92 expect(body.total).to.equal(0)
93 expect(body.data).to.be.an('array')
94 expect(body.data.length).to.equal(0)
95 }
96
97 {
98 const body = await command.list({ token: secondUserToken })
99
100 expect(body.total).to.equal(0)
101 expect(body.data).to.be.an('array')
102 expect(body.data.length).to.equal(0)
103 }
104 })
105
106 it('Should send a request to change ownership of a video', async function () {
107 this.timeout(15000)
108
109 await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser })
110 })
111
112 it('Should only return a request to change ownership for the second user', async function () {
113 {
114 const body = await command.list({ token: firstUserToken })
115
116 expect(body.total).to.equal(0)
117 expect(body.data).to.be.an('array')
118 expect(body.data.length).to.equal(0)
119 }
120
121 {
122 const body = await command.list({ token: secondUserToken })
123
124 expect(body.total).to.equal(1)
125 expect(body.data).to.be.an('array')
126 expect(body.data.length).to.equal(1)
127
128 lastRequestId = body.data[0].id
129 }
130 })
131
132 it('Should accept the same change ownership request without crashing', async function () {
133 await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser })
134 })
135
136 it('Should not create multiple change ownership requests while one is waiting', async function () {
137 const body = await command.list({ token: secondUserToken })
138
139 expect(body.total).to.equal(1)
140 expect(body.data).to.be.an('array')
141 expect(body.data.length).to.equal(1)
142 })
143
144 it('Should not be possible to refuse the change of ownership from first user', async function () {
145 await command.refuse({ token: firstUserToken, ownershipId: lastRequestId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
146 })
147
148 it('Should be possible to refuse the change of ownership from second user', async function () {
149 await command.refuse({ token: secondUserToken, ownershipId: lastRequestId })
150 })
151
152 it('Should send a new request to change ownership of a video', async function () {
153 this.timeout(15000)
154
155 await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser })
156 })
157
158 it('Should return two requests to change ownership for the second user', async function () {
159 {
160 const body = await command.list({ token: firstUserToken })
161
162 expect(body.total).to.equal(0)
163 expect(body.data).to.be.an('array')
164 expect(body.data.length).to.equal(0)
165 }
166
167 {
168 const body = await command.list({ token: secondUserToken })
169
170 expect(body.total).to.equal(2)
171 expect(body.data).to.be.an('array')
172 expect(body.data.length).to.equal(2)
173
174 lastRequestId = body.data[0].id
175 }
176 })
177
178 it('Should not be possible to accept the change of ownership from first user', async function () {
179 await command.accept({
180 token: firstUserToken,
181 ownershipId: lastRequestId,
182 channelId: secondUserChannelId,
183 expectedStatus: HttpStatusCode.FORBIDDEN_403
184 })
185 })
186
187 it('Should be possible to accept the change of ownership from second user', async function () {
188 await command.accept({ token: secondUserToken, ownershipId: lastRequestId, channelId: secondUserChannelId })
189
190 await waitJobs(servers)
191 })
192
193 it('Should have the channel of the video updated', async function () {
194 for (const server of servers) {
195 const video = await server.videos.get({ id: servers[0].store.videoCreated.uuid })
196
197 expect(video.name).to.equal('my super name')
198 expect(video.channel.displayName).to.equal('Main second channel')
199 expect(video.channel.name).to.equal('second_channel')
200 }
201 })
202
203 it('Should send a request to change ownership of a live', async function () {
204 this.timeout(15000)
205
206 await command.create({ token: firstUserToken, videoId: liveId, username: secondUser })
207
208 const body = await command.list({ token: secondUserToken })
209
210 expect(body.total).to.equal(3)
211 expect(body.data.length).to.equal(3)
212
213 lastRequestId = body.data[0].id
214 })
215
216 it('Should accept a live ownership change', async function () {
217 this.timeout(20000)
218
219 await command.accept({ token: secondUserToken, ownershipId: lastRequestId, channelId: secondUserChannelId })
220
221 await waitJobs(servers)
222
223 for (const server of servers) {
224 const video = await server.videos.get({ id: servers[0].store.videoCreated.uuid })
225
226 expect(video.name).to.equal('my super name')
227 expect(video.channel.displayName).to.equal('Main second channel')
228 expect(video.channel.name).to.equal('second_channel')
229 }
230 })
231
232 after(async function () {
233 await cleanupTests(servers)
234 })
235})
236
237describe('Test video change ownership - quota too small', function () {
238 let server: PeerTubeServer
239 const firstUser = 'first'
240 const secondUser = 'second'
241
242 let firstUserToken = ''
243 let secondUserToken = ''
244 let lastRequestId: number
245
246 before(async function () {
247 this.timeout(50000)
248
249 // Run one server
250 server = await createSingleServer(1)
251 await setAccessTokensToServers([ server ])
252
253 await server.users.create({ username: secondUser, videoQuota: 10 })
254
255 firstUserToken = await server.users.generateUserAndToken(firstUser)
256 secondUserToken = await server.login.getAccessToken(secondUser)
257
258 // Upload some videos on the server
259 const attributes = {
260 name: 'my super name',
261 description: 'my super description'
262 }
263 await server.videos.upload({ token: firstUserToken, attributes })
264
265 await waitJobs(server)
266
267 const { data } = await server.videos.list()
268 expect(data.length).to.equal(1)
269
270 server.store.videoCreated = data.find(video => video.name === 'my super name')
271 })
272
273 it('Should send a request to change ownership of a video', async function () {
274 this.timeout(15000)
275
276 await server.changeOwnership.create({ token: firstUserToken, videoId: server.store.videoCreated.id, username: secondUser })
277 })
278
279 it('Should only return a request to change ownership for the second user', async function () {
280 {
281 const body = await server.changeOwnership.list({ token: firstUserToken })
282
283 expect(body.total).to.equal(0)
284 expect(body.data).to.be.an('array')
285 expect(body.data.length).to.equal(0)
286 }
287
288 {
289 const body = await server.changeOwnership.list({ token: secondUserToken })
290
291 expect(body.total).to.equal(1)
292 expect(body.data).to.be.an('array')
293 expect(body.data.length).to.equal(1)
294
295 lastRequestId = body.data[0].id
296 }
297 })
298
299 it('Should not be possible to accept the change of ownership from second user because of exceeded quota', async function () {
300 const { videoChannels } = await server.users.getMyInfo({ token: secondUserToken })
301 const channelId = videoChannels[0].id
302
303 await server.changeOwnership.accept({
304 token: secondUserToken,
305 ownershipId: lastRequestId,
306 channelId,
307 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
308 })
309 })
310
311 after(async function () {
312 await cleanupTests([ server ])
313 })
314})
diff --git a/server/tests/api/videos/video-channel-syncs.ts b/server/tests/api/videos/video-channel-syncs.ts
deleted file mode 100644
index 7f688c7d6..000000000
--- a/server/tests/api/videos/video-channel-syncs.ts
+++ /dev/null
@@ -1,320 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { FIXTURE_URLS, SQLCommand } from '@server/tests/shared'
5import { areHttpImportTestsDisabled } from '@shared/core-utils'
6import { VideoChannelSyncState, VideoInclude, VideoPrivacy } from '@shared/models'
7import {
8 cleanupTests,
9 createMultipleServers,
10 getServerImportConfig,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 setDefaultAccountAvatar,
14 setDefaultChannelAvatar,
15 setDefaultVideoChannel,
16 waitJobs
17} from '@shared/server-commands'
18
19describe('Test channel synchronizations', function () {
20 if (areHttpImportTestsDisabled()) return
21
22 function runSuite (mode: 'youtube-dl' | 'yt-dlp') {
23
24 describe('Sync using ' + mode, function () {
25 let servers: PeerTubeServer[]
26 let sqlCommands: SQLCommand[] = []
27
28 let startTestDate: Date
29
30 let rootChannelSyncId: number
31 const userInfo = {
32 accessToken: '',
33 username: 'user1',
34 channelName: 'user1_channel',
35 channelId: -1,
36 syncId: -1
37 }
38
39 async function changeDateForSync (channelSyncId: number, newDate: string) {
40 await sqlCommands[0].updateQuery(
41 `UPDATE "videoChannelSync" ` +
42 `SET "createdAt"='${newDate}', "lastSyncAt"='${newDate}' ` +
43 `WHERE id=${channelSyncId}`
44 )
45 }
46
47 async function listAllVideosOfChannel (channelName: string) {
48 return servers[0].videos.listByChannel({
49 handle: channelName,
50 include: VideoInclude.NOT_PUBLISHED_STATE
51 })
52 }
53
54 async function forceSyncAll (videoChannelSyncId: number, fromDate = '1970-01-01') {
55 await changeDateForSync(videoChannelSyncId, fromDate)
56
57 await servers[0].debug.sendCommand({
58 body: {
59 command: 'process-video-channel-sync-latest'
60 }
61 })
62
63 await waitJobs(servers)
64 }
65
66 before(async function () {
67 this.timeout(240_000)
68
69 startTestDate = new Date()
70
71 servers = await createMultipleServers(2, getServerImportConfig(mode))
72
73 await setAccessTokensToServers(servers)
74 await setDefaultVideoChannel(servers)
75 await setDefaultChannelAvatar(servers)
76 await setDefaultAccountAvatar(servers)
77
78 await servers[0].config.enableChannelSync()
79
80 {
81 userInfo.accessToken = await servers[0].users.generateUserAndToken(userInfo.username)
82
83 const { videoChannels } = await servers[0].users.getMyInfo({ token: userInfo.accessToken })
84 userInfo.channelId = videoChannels[0].id
85 }
86
87 sqlCommands = servers.map(s => new SQLCommand(s))
88 })
89
90 it('Should fetch the latest channel videos of a remote channel', async function () {
91 this.timeout(120_000)
92
93 {
94 const { video } = await servers[0].imports.importVideo({
95 attributes: {
96 channelId: servers[0].store.channel.id,
97 privacy: VideoPrivacy.PUBLIC,
98 targetUrl: FIXTURE_URLS.youtube
99 }
100 })
101
102 expect(video.name).to.equal('small video - youtube')
103 expect(video.waitTranscoding).to.be.true
104
105 const { total } = await listAllVideosOfChannel('root_channel')
106 expect(total).to.equal(1)
107 }
108
109 const { videoChannelSync } = await servers[0].channelSyncs.create({
110 attributes: {
111 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
112 videoChannelId: servers[0].store.channel.id
113 }
114 })
115 rootChannelSyncId = videoChannelSync.id
116
117 await forceSyncAll(rootChannelSyncId)
118
119 {
120 const { total, data } = await listAllVideosOfChannel('root_channel')
121 expect(total).to.equal(2)
122 expect(data[0].name).to.equal('test')
123 expect(data[0].waitTranscoding).to.be.true
124 }
125 })
126
127 it('Should add another synchronization', async function () {
128 const externalChannelUrl = FIXTURE_URLS.youtubeChannel + '?foo=bar'
129
130 const { videoChannelSync } = await servers[0].channelSyncs.create({
131 attributes: {
132 externalChannelUrl,
133 videoChannelId: servers[0].store.channel.id
134 }
135 })
136
137 expect(videoChannelSync.externalChannelUrl).to.equal(externalChannelUrl)
138 expect(videoChannelSync.channel.id).to.equal(servers[0].store.channel.id)
139 expect(videoChannelSync.channel.name).to.equal('root_channel')
140 expect(videoChannelSync.state.id).to.equal(VideoChannelSyncState.WAITING_FIRST_RUN)
141 expect(new Date(videoChannelSync.createdAt)).to.be.above(startTestDate).and.to.be.at.most(new Date())
142 })
143
144 it('Should add a synchronization for another user', async function () {
145 const { videoChannelSync } = await servers[0].channelSyncs.create({
146 attributes: {
147 externalChannelUrl: FIXTURE_URLS.youtubeChannel + '?baz=qux',
148 videoChannelId: userInfo.channelId
149 },
150 token: userInfo.accessToken
151 })
152 userInfo.syncId = videoChannelSync.id
153 })
154
155 it('Should not import a channel if not asked', async function () {
156 await waitJobs(servers)
157
158 const { data } = await servers[0].channelSyncs.listByAccount({ accountName: userInfo.username })
159
160 expect(data[0].state).to.contain({
161 id: VideoChannelSyncState.WAITING_FIRST_RUN,
162 label: 'Waiting first run'
163 })
164 })
165
166 it('Should only fetch the videos newer than the creation date', async function () {
167 this.timeout(120_000)
168
169 await forceSyncAll(userInfo.syncId, '2019-03-01')
170
171 const { data, total } = await listAllVideosOfChannel(userInfo.channelName)
172
173 expect(total).to.equal(1)
174 expect(data[0].name).to.equal('test')
175 })
176
177 it('Should list channel synchronizations', async function () {
178 // Root
179 {
180 const { total, data } = await servers[0].channelSyncs.listByAccount({ accountName: 'root' })
181 expect(total).to.equal(2)
182
183 expect(data[0]).to.deep.contain({
184 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
185 state: {
186 id: VideoChannelSyncState.SYNCED,
187 label: 'Synchronized'
188 }
189 })
190
191 expect(new Date(data[0].lastSyncAt)).to.be.greaterThan(startTestDate)
192
193 expect(data[0].channel).to.contain({ id: servers[0].store.channel.id })
194 expect(data[1]).to.contain({ externalChannelUrl: FIXTURE_URLS.youtubeChannel + '?foo=bar' })
195 }
196
197 // User
198 {
199 const { total, data } = await servers[0].channelSyncs.listByAccount({ accountName: userInfo.username })
200 expect(total).to.equal(1)
201 expect(data[0]).to.deep.contain({
202 externalChannelUrl: FIXTURE_URLS.youtubeChannel + '?baz=qux',
203 state: {
204 id: VideoChannelSyncState.SYNCED,
205 label: 'Synchronized'
206 }
207 })
208 }
209 })
210
211 it('Should list imports of a channel synchronization', async function () {
212 const { total, data } = await servers[0].imports.getMyVideoImports({ videoChannelSyncId: rootChannelSyncId })
213
214 expect(total).to.equal(1)
215 expect(data).to.have.lengthOf(1)
216 expect(data[0].video.name).to.equal('test')
217 })
218
219 it('Should remove user\'s channel synchronizations', async function () {
220 await servers[0].channelSyncs.delete({ channelSyncId: userInfo.syncId })
221
222 const { total } = await servers[0].channelSyncs.listByAccount({ accountName: userInfo.username })
223 expect(total).to.equal(0)
224 })
225
226 // FIXME: youtube-dl/yt-dlp doesn't work when speicifying a port after the hostname
227 // it('Should import a remote PeerTube channel', async function () {
228 // this.timeout(240_000)
229
230 // await servers[1].videos.quickUpload({ name: 'remote 1' })
231 // await waitJobs(servers)
232
233 // const { videoChannelSync } = await servers[0].channelSyncs.create({
234 // attributes: {
235 // externalChannelUrl: servers[1].url + '/c/root_channel',
236 // videoChannelId: userInfo.channelId
237 // },
238 // token: userInfo.accessToken
239 // })
240 // await servers[0].channels.importVideos({
241 // channelName: userInfo.channelName,
242 // externalChannelUrl: servers[1].url + '/c/root_channel',
243 // videoChannelSyncId: videoChannelSync.id,
244 // token: userInfo.accessToken
245 // })
246
247 // await waitJobs(servers)
248
249 // const { data, total } = await servers[0].videos.listByChannel({
250 // handle: userInfo.channelName,
251 // include: VideoInclude.NOT_PUBLISHED_STATE
252 // })
253
254 // expect(total).to.equal(2)
255 // expect(data[0].name).to.equal('remote 1')
256 // })
257
258 // it('Should keep synced a remote PeerTube channel', async function () {
259 // this.timeout(240_000)
260
261 // await servers[1].videos.quickUpload({ name: 'remote 2' })
262 // await waitJobs(servers)
263
264 // await servers[0].debug.sendCommand({
265 // body: {
266 // command: 'process-video-channel-sync-latest'
267 // }
268 // })
269
270 // await waitJobs(servers)
271
272 // const { data, total } = await servers[0].videos.listByChannel({
273 // handle: userInfo.channelName,
274 // include: VideoInclude.NOT_PUBLISHED_STATE
275 // })
276 // expect(total).to.equal(2)
277 // expect(data[0].name).to.equal('remote 2')
278 // })
279
280 it('Should fetch the latest videos of a youtube playlist', async function () {
281 this.timeout(120_000)
282
283 const { id: channelId } = await servers[0].channels.create({
284 attributes: {
285 name: 'channel2'
286 }
287 })
288
289 const { videoChannelSync: { id: videoChannelSyncId } } = await servers[0].channelSyncs.create({
290 attributes: {
291 externalChannelUrl: FIXTURE_URLS.youtubePlaylist,
292 videoChannelId: channelId
293 }
294 })
295
296 await forceSyncAll(videoChannelSyncId)
297
298 {
299
300 const { total, data } = await listAllVideosOfChannel('channel2')
301 expect(total).to.equal(2)
302 expect(data[0].name).to.equal('test')
303 expect(data[1].name).to.equal('small video - youtube')
304 }
305 })
306
307 after(async function () {
308 for (const sqlCommand of sqlCommands) {
309 await sqlCommand.cleanup()
310 }
311
312 await cleanupTests(servers)
313 })
314 })
315 }
316
317 // FIXME: suite is broken with youtube-dl
318 // runSuite('youtube-dl')
319 runSuite('yt-dlp')
320})
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
deleted file mode 100644
index f7cf84618..000000000
--- a/server/tests/api/videos/video-channels.ts
+++ /dev/null
@@ -1,555 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { basename } from 'path'
5import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants'
6import { SQLCommand, testFileExistsOrNot, testImage } from '@server/tests/shared'
7import { wait } from '@shared/core-utils'
8import { ActorImageType, User, VideoChannel } from '@shared/models'
9import {
10 cleanupTests,
11 createMultipleServers,
12 doubleFollow,
13 PeerTubeServer,
14 setAccessTokensToServers,
15 setDefaultAccountAvatar,
16 setDefaultVideoChannel,
17 waitJobs
18} from '@shared/server-commands'
19
20async function findChannel (server: PeerTubeServer, channelId: number) {
21 const body = await server.channels.list({ sort: '-name' })
22
23 return body.data.find(c => c.id === channelId)
24}
25
26describe('Test video channels', function () {
27 let servers: PeerTubeServer[]
28 let sqlCommands: SQLCommand[] = []
29
30 let userInfo: User
31 let secondVideoChannelId: number
32 let totoChannel: number
33 let videoUUID: string
34 let accountName: string
35 let secondUserChannelName: string
36
37 const avatarPaths: { [ port: number ]: string } = {}
38 const bannerPaths: { [ port: number ]: string } = {}
39
40 before(async function () {
41 this.timeout(60000)
42
43 servers = await createMultipleServers(2)
44
45 await setAccessTokensToServers(servers)
46 await setDefaultVideoChannel(servers)
47 await setDefaultAccountAvatar(servers)
48
49 await doubleFollow(servers[0], servers[1])
50
51 sqlCommands = servers.map(s => new SQLCommand(s))
52 })
53
54 it('Should have one video channel (created with root)', async () => {
55 const body = await servers[0].channels.list({ start: 0, count: 2 })
56
57 expect(body.total).to.equal(1)
58 expect(body.data).to.be.an('array')
59 expect(body.data).to.have.lengthOf(1)
60 })
61
62 it('Should create another video channel', async function () {
63 this.timeout(30000)
64
65 {
66 const videoChannel = {
67 name: 'second_video_channel',
68 displayName: 'second video channel',
69 description: 'super video channel description',
70 support: 'super video channel support text'
71 }
72 const created = await servers[0].channels.create({ attributes: videoChannel })
73 secondVideoChannelId = created.id
74 }
75
76 // The channel is 1 is propagated to servers 2
77 {
78 const attributes = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' }
79 const { uuid } = await servers[0].videos.upload({ attributes })
80 videoUUID = uuid
81 }
82
83 await waitJobs(servers)
84 })
85
86 it('Should have two video channels when getting my information', async () => {
87 userInfo = await servers[0].users.getMyInfo()
88
89 expect(userInfo.videoChannels).to.be.an('array')
90 expect(userInfo.videoChannels).to.have.lengthOf(2)
91
92 const videoChannels = userInfo.videoChannels
93 expect(videoChannels[0].name).to.equal('root_channel')
94 expect(videoChannels[0].displayName).to.equal('Main root channel')
95
96 expect(videoChannels[1].name).to.equal('second_video_channel')
97 expect(videoChannels[1].displayName).to.equal('second video channel')
98 expect(videoChannels[1].description).to.equal('super video channel description')
99 expect(videoChannels[1].support).to.equal('super video channel support text')
100
101 accountName = userInfo.account.name + '@' + userInfo.account.host
102 })
103
104 it('Should have two video channels when getting account channels on server 1', async function () {
105 const body = await servers[0].channels.listByAccount({ accountName })
106 expect(body.total).to.equal(2)
107
108 const videoChannels = body.data
109
110 expect(videoChannels).to.be.an('array')
111 expect(videoChannels).to.have.lengthOf(2)
112
113 expect(videoChannels[0].name).to.equal('root_channel')
114 expect(videoChannels[0].displayName).to.equal('Main root channel')
115
116 expect(videoChannels[1].name).to.equal('second_video_channel')
117 expect(videoChannels[1].displayName).to.equal('second video channel')
118 expect(videoChannels[1].description).to.equal('super video channel description')
119 expect(videoChannels[1].support).to.equal('super video channel support text')
120 })
121
122 it('Should paginate and sort account channels', async function () {
123 {
124 const body = await servers[0].channels.listByAccount({
125 accountName,
126 start: 0,
127 count: 1,
128 sort: 'createdAt'
129 })
130
131 expect(body.total).to.equal(2)
132 expect(body.data).to.have.lengthOf(1)
133
134 const videoChannel: VideoChannel = body.data[0]
135 expect(videoChannel.name).to.equal('root_channel')
136 }
137
138 {
139 const body = await servers[0].channels.listByAccount({
140 accountName,
141 start: 0,
142 count: 1,
143 sort: '-createdAt'
144 })
145
146 expect(body.total).to.equal(2)
147 expect(body.data).to.have.lengthOf(1)
148 expect(body.data[0].name).to.equal('second_video_channel')
149 }
150
151 {
152 const body = await servers[0].channels.listByAccount({
153 accountName,
154 start: 1,
155 count: 1,
156 sort: '-createdAt'
157 })
158
159 expect(body.total).to.equal(2)
160 expect(body.data).to.have.lengthOf(1)
161 expect(body.data[0].name).to.equal('root_channel')
162 }
163 })
164
165 it('Should have one video channel when getting account channels on server 2', async function () {
166 const body = await servers[1].channels.listByAccount({ accountName })
167
168 expect(body.total).to.equal(1)
169 expect(body.data).to.be.an('array')
170 expect(body.data).to.have.lengthOf(1)
171
172 const videoChannel = body.data[0]
173 expect(videoChannel.name).to.equal('second_video_channel')
174 expect(videoChannel.displayName).to.equal('second video channel')
175 expect(videoChannel.description).to.equal('super video channel description')
176 expect(videoChannel.support).to.equal('super video channel support text')
177 })
178
179 it('Should list video channels', async function () {
180 const body = await servers[0].channels.list({ start: 1, count: 1, sort: '-name' })
181
182 expect(body.total).to.equal(2)
183 expect(body.data).to.be.an('array')
184 expect(body.data).to.have.lengthOf(1)
185 expect(body.data[0].name).to.equal('root_channel')
186 expect(body.data[0].displayName).to.equal('Main root channel')
187 })
188
189 it('Should update video channel', async function () {
190 this.timeout(15000)
191
192 const videoChannelAttributes = {
193 displayName: 'video channel updated',
194 description: 'video channel description updated',
195 support: 'support updated'
196 }
197
198 await servers[0].channels.update({ channelName: 'second_video_channel', attributes: videoChannelAttributes })
199
200 await waitJobs(servers)
201 })
202
203 it('Should have video channel updated', async function () {
204 for (const server of servers) {
205 const body = await server.channels.list({ start: 0, count: 1, sort: '-name' })
206
207 expect(body.total).to.equal(2)
208 expect(body.data).to.be.an('array')
209 expect(body.data).to.have.lengthOf(1)
210
211 expect(body.data[0].name).to.equal('second_video_channel')
212 expect(body.data[0].displayName).to.equal('video channel updated')
213 expect(body.data[0].description).to.equal('video channel description updated')
214 expect(body.data[0].support).to.equal('support updated')
215 }
216 })
217
218 it('Should not have updated the video support field', async function () {
219 for (const server of servers) {
220 const video = await server.videos.get({ id: videoUUID })
221 expect(video.support).to.equal('video support field')
222 }
223 })
224
225 it('Should update another accounts video channel', async function () {
226 this.timeout(15000)
227
228 const result = await servers[0].users.generate('second_user')
229 secondUserChannelName = result.userChannelName
230
231 await servers[0].videos.quickUpload({ name: 'video', token: result.token })
232
233 const videoChannelAttributes = {
234 displayName: 'video channel updated',
235 description: 'video channel description updated',
236 support: 'support updated'
237 }
238
239 await servers[0].channels.update({ channelName: secondUserChannelName, attributes: videoChannelAttributes })
240
241 await waitJobs(servers)
242 })
243
244 it('Should have another accounts video channel updated', async function () {
245 for (const server of servers) {
246 const body = await server.channels.get({ channelName: `${secondUserChannelName}@${servers[0].host}` })
247
248 expect(body.displayName).to.equal('video channel updated')
249 expect(body.description).to.equal('video channel description updated')
250 expect(body.support).to.equal('support updated')
251 }
252 })
253
254 it('Should update the channel support field and update videos too', async function () {
255 this.timeout(35000)
256
257 const videoChannelAttributes = {
258 support: 'video channel support text updated',
259 bulkVideosSupportUpdate: true
260 }
261
262 await servers[0].channels.update({ channelName: 'second_video_channel', attributes: videoChannelAttributes })
263
264 await waitJobs(servers)
265
266 for (const server of servers) {
267 const video = await server.videos.get({ id: videoUUID })
268 expect(video.support).to.equal(videoChannelAttributes.support)
269 }
270 })
271
272 it('Should update video channel avatar', async function () {
273 this.timeout(15000)
274
275 const fixture = 'avatar.png'
276
277 await servers[0].channels.updateImage({
278 channelName: 'second_video_channel',
279 fixture,
280 type: 'avatar'
281 })
282
283 await waitJobs(servers)
284
285 for (let i = 0; i < servers.length; i++) {
286 const server = servers[i]
287
288 const videoChannel = await findChannel(server, secondVideoChannelId)
289 const expectedSizes = ACTOR_IMAGES_SIZE[ActorImageType.AVATAR]
290
291 expect(videoChannel.avatars.length).to.equal(expectedSizes.length, 'Expected avatars to be generated in all sizes')
292
293 for (const avatar of videoChannel.avatars) {
294 avatarPaths[server.port] = avatar.path
295 await testImage(server.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatarPaths[server.port], '.png')
296 await testFileExistsOrNot(server, 'avatars', basename(avatarPaths[server.port]), true)
297
298 const row = await sqlCommands[i].getActorImage(basename(avatarPaths[server.port]))
299
300 expect(expectedSizes.some(({ height, width }) => row.height === height && row.width === width)).to.equal(true)
301 }
302 }
303 })
304
305 it('Should update video channel banner', async function () {
306 this.timeout(15000)
307
308 const fixture = 'banner.jpg'
309
310 await servers[0].channels.updateImage({
311 channelName: 'second_video_channel',
312 fixture,
313 type: 'banner'
314 })
315
316 await waitJobs(servers)
317
318 for (let i = 0; i < servers.length; i++) {
319 const server = servers[i]
320
321 const videoChannel = await server.channels.get({ channelName: 'second_video_channel@' + servers[0].host })
322
323 bannerPaths[server.port] = videoChannel.banners[0].path
324 await testImage(server.url, 'banner-resized', bannerPaths[server.port])
325 await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), true)
326
327 const row = await sqlCommands[i].getActorImage(basename(bannerPaths[server.port]))
328 expect(row.height).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].height)
329 expect(row.width).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].width)
330 }
331 })
332
333 it('Should still correctly list channels', async function () {
334 {
335 const body = await servers[0].channels.list({ start: 1, count: 1, sort: 'createdAt' })
336
337 expect(body.total).to.equal(3)
338 expect(body.data).to.have.lengthOf(1)
339 expect(body.data[0].name).to.equal('second_video_channel')
340 }
341
342 {
343 const body = await servers[0].channels.listByAccount({ accountName, start: 1, count: 1, sort: 'createdAt' })
344
345 expect(body.total).to.equal(2)
346 expect(body.data).to.have.lengthOf(1)
347 expect(body.data[0].name).to.equal('second_video_channel')
348 }
349 })
350
351 it('Should delete the video channel avatar', async function () {
352 this.timeout(15000)
353 await servers[0].channels.deleteImage({ channelName: 'second_video_channel', type: 'avatar' })
354
355 await waitJobs(servers)
356
357 for (const server of servers) {
358 const videoChannel = await findChannel(server, secondVideoChannelId)
359 await testFileExistsOrNot(server, 'avatars', basename(avatarPaths[server.port]), false)
360
361 expect(videoChannel.avatars).to.be.empty
362 }
363 })
364
365 it('Should delete the video channel banner', async function () {
366 this.timeout(15000)
367
368 await servers[0].channels.deleteImage({ channelName: 'second_video_channel', type: 'banner' })
369
370 await waitJobs(servers)
371
372 for (const server of servers) {
373 const videoChannel = await findChannel(server, secondVideoChannelId)
374 await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), false)
375
376 expect(videoChannel.banners).to.be.empty
377 }
378 })
379
380 it('Should list the second video channel videos', async function () {
381 for (const server of servers) {
382 const channelURI = 'second_video_channel@' + servers[0].host
383 const { total, data } = await server.videos.listByChannel({ handle: channelURI })
384
385 expect(total).to.equal(1)
386 expect(data).to.be.an('array')
387 expect(data).to.have.lengthOf(1)
388 expect(data[0].name).to.equal('my video name')
389 }
390 })
391
392 it('Should change the video channel of a video', async function () {
393 await servers[0].videos.update({ id: videoUUID, attributes: { channelId: servers[0].store.channel.id } })
394
395 await waitJobs(servers)
396 })
397
398 it('Should list the first video channel videos', async function () {
399 for (const server of servers) {
400 {
401 const secondChannelURI = 'second_video_channel@' + servers[0].host
402 const { total } = await server.videos.listByChannel({ handle: secondChannelURI })
403 expect(total).to.equal(0)
404 }
405
406 {
407 const channelURI = 'root_channel@' + servers[0].host
408 const { total, data } = await server.videos.listByChannel({ handle: channelURI })
409 expect(total).to.equal(1)
410
411 expect(data).to.be.an('array')
412 expect(data).to.have.lengthOf(1)
413 expect(data[0].name).to.equal('my video name')
414 }
415 }
416 })
417
418 it('Should delete video channel', async function () {
419 await servers[0].channels.delete({ channelName: 'second_video_channel' })
420 })
421
422 it('Should have video channel deleted', async function () {
423 const body = await servers[0].channels.list({ start: 0, count: 10, sort: 'createdAt' })
424
425 expect(body.total).to.equal(2)
426 expect(body.data).to.be.an('array')
427 expect(body.data).to.have.lengthOf(2)
428 expect(body.data[0].displayName).to.equal('Main root channel')
429 expect(body.data[1].displayName).to.equal('video channel updated')
430 })
431
432 it('Should create the main channel with a suffix if there is a conflict', async function () {
433 {
434 const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' }
435 const created = await servers[0].channels.create({ attributes: videoChannel })
436 totoChannel = created.id
437 }
438
439 {
440 await servers[0].users.create({ username: 'toto', password: 'password' })
441 const accessToken = await servers[0].login.getAccessToken({ username: 'toto', password: 'password' })
442
443 const { videoChannels } = await servers[0].users.getMyInfo({ token: accessToken })
444 expect(videoChannels[0].name).to.equal('toto_channel-1')
445 }
446 })
447
448 it('Should report correct channel views per days', async function () {
449 {
450 const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true })
451
452 for (const channel of data) {
453 expect(channel).to.haveOwnProperty('viewsPerDay')
454 expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today
455
456 for (const v of channel.viewsPerDay) {
457 expect(v.date).to.be.an('string')
458 expect(v.views).to.equal(0)
459 }
460 }
461 }
462
463 {
464 // video has been posted on channel servers[0].store.videoChannel.id since last update
465 await servers[0].views.simulateView({ id: videoUUID, xForwardedFor: '0.0.0.1,127.0.0.1' })
466 await servers[0].views.simulateView({ id: videoUUID, xForwardedFor: '0.0.0.2,127.0.0.1' })
467
468 // Wait the repeatable job
469 await wait(8000)
470
471 const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true })
472 const channelWithView = data.find(channel => channel.id === servers[0].store.channel.id)
473 expect(channelWithView.viewsPerDay.slice(-1)[0].views).to.equal(2)
474 }
475 })
476
477 it('Should report correct total views count', async function () {
478 // check if there's the property
479 {
480 const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true })
481
482 for (const channel of data) {
483 expect(channel).to.haveOwnProperty('totalViews')
484 expect(channel.totalViews).to.be.a('number')
485 }
486 }
487
488 // Check if the totalViews count can be updated
489 {
490 const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true })
491 const channelWithView = data.find(channel => channel.id === servers[0].store.channel.id)
492 expect(channelWithView.totalViews).to.equal(2)
493 }
494 })
495
496 it('Should report correct videos count', async function () {
497 const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true })
498
499 const totoChannel = data.find(c => c.name === 'toto_channel')
500 const rootChannel = data.find(c => c.name === 'root_channel')
501
502 expect(rootChannel.videosCount).to.equal(1)
503 expect(totoChannel.videosCount).to.equal(0)
504 })
505
506 it('Should search among account video channels', async function () {
507 {
508 const body = await servers[0].channels.listByAccount({ accountName, search: 'root' })
509 expect(body.total).to.equal(1)
510
511 const channels = body.data
512 expect(channels).to.have.lengthOf(1)
513 }
514
515 {
516 const body = await servers[0].channels.listByAccount({ accountName, search: 'does not exist' })
517 expect(body.total).to.equal(0)
518
519 const channels = body.data
520 expect(channels).to.have.lengthOf(0)
521 }
522 })
523
524 it('Should list channels by updatedAt desc if a video has been uploaded', async function () {
525 this.timeout(30000)
526
527 await servers[0].videos.upload({ attributes: { channelId: totoChannel } })
528 await waitJobs(servers)
529
530 for (const server of servers) {
531 const { data } = await server.channels.listByAccount({ accountName, sort: '-updatedAt' })
532
533 expect(data[0].name).to.equal('toto_channel')
534 expect(data[1].name).to.equal('root_channel')
535 }
536
537 await servers[0].videos.upload({ attributes: { channelId: servers[0].store.channel.id } })
538 await waitJobs(servers)
539
540 for (const server of servers) {
541 const { data } = await server.channels.listByAccount({ accountName, sort: '-updatedAt' })
542
543 expect(data[0].name).to.equal('root_channel')
544 expect(data[1].name).to.equal('toto_channel')
545 }
546 })
547
548 after(async function () {
549 for (const sqlCommand of sqlCommands) {
550 await sqlCommand.cleanup()
551 }
552
553 await cleanupTests(servers)
554 })
555})
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
deleted file mode 100644
index b7d5624a6..000000000
--- a/server/tests/api/videos/video-comments.ts
+++ /dev/null
@@ -1,335 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { dateIsValid, testImage } from '@server/tests/shared'
5import {
6 cleanupTests,
7 CommentsCommand,
8 createSingleServer,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 setDefaultAccountAvatar,
12 setDefaultChannelAvatar
13} from '@shared/server-commands'
14
15describe('Test video comments', function () {
16 let server: PeerTubeServer
17 let videoId: number
18 let videoUUID: string
19 let threadId: number
20 let replyToDeleteId: number
21
22 let userAccessTokenServer1: string
23
24 let command: CommentsCommand
25
26 before(async function () {
27 this.timeout(120000)
28
29 server = await createSingleServer(1)
30
31 await setAccessTokensToServers([ server ])
32
33 const { id, uuid } = await server.videos.upload()
34 videoUUID = uuid
35 videoId = id
36
37 await setDefaultChannelAvatar(server)
38 await setDefaultAccountAvatar(server)
39
40 userAccessTokenServer1 = await server.users.generateUserAndToken('user1')
41 await setDefaultChannelAvatar(server, 'user1_channel')
42 await setDefaultAccountAvatar(server, userAccessTokenServer1)
43
44 command = server.comments
45 })
46
47 describe('User comments', function () {
48
49 it('Should not have threads on this video', async function () {
50 const body = await command.listThreads({ videoId: videoUUID })
51
52 expect(body.total).to.equal(0)
53 expect(body.totalNotDeletedComments).to.equal(0)
54 expect(body.data).to.be.an('array')
55 expect(body.data).to.have.lengthOf(0)
56 })
57
58 it('Should create a thread in this video', async function () {
59 const text = 'my super first comment'
60
61 const comment = await command.createThread({ videoId: videoUUID, text })
62
63 expect(comment.inReplyToCommentId).to.be.null
64 expect(comment.text).equal('my super first comment')
65 expect(comment.videoId).to.equal(videoId)
66 expect(comment.id).to.equal(comment.threadId)
67 expect(comment.account.name).to.equal('root')
68 expect(comment.account.host).to.equal(server.host)
69 expect(comment.account.url).to.equal(server.url + '/accounts/root')
70 expect(comment.totalReplies).to.equal(0)
71 expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
72 expect(dateIsValid(comment.createdAt as string)).to.be.true
73 expect(dateIsValid(comment.updatedAt as string)).to.be.true
74 })
75
76 it('Should list threads of this video', async function () {
77 const body = await command.listThreads({ videoId: videoUUID })
78
79 expect(body.total).to.equal(1)
80 expect(body.totalNotDeletedComments).to.equal(1)
81 expect(body.data).to.be.an('array')
82 expect(body.data).to.have.lengthOf(1)
83
84 const comment = body.data[0]
85 expect(comment.inReplyToCommentId).to.be.null
86 expect(comment.text).equal('my super first comment')
87 expect(comment.videoId).to.equal(videoId)
88 expect(comment.id).to.equal(comment.threadId)
89 expect(comment.account.name).to.equal('root')
90 expect(comment.account.host).to.equal(server.host)
91
92 for (const avatar of comment.account.avatars) {
93 await testImage(server.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.png')
94 }
95
96 expect(comment.totalReplies).to.equal(0)
97 expect(comment.totalRepliesFromVideoAuthor).to.equal(0)
98 expect(dateIsValid(comment.createdAt as string)).to.be.true
99 expect(dateIsValid(comment.updatedAt as string)).to.be.true
100
101 threadId = comment.threadId
102 })
103
104 it('Should get all the thread created', async function () {
105 const body = await command.getThread({ videoId: videoUUID, threadId })
106
107 const rootComment = body.comment
108 expect(rootComment.inReplyToCommentId).to.be.null
109 expect(rootComment.text).equal('my super first comment')
110 expect(rootComment.videoId).to.equal(videoId)
111 expect(dateIsValid(rootComment.createdAt as string)).to.be.true
112 expect(dateIsValid(rootComment.updatedAt as string)).to.be.true
113 })
114
115 it('Should create multiple replies in this thread', async function () {
116 const text1 = 'my super answer to thread 1'
117 const created = await command.addReply({ videoId, toCommentId: threadId, text: text1 })
118 const childCommentId = created.id
119
120 const text2 = 'my super answer to answer of thread 1'
121 await command.addReply({ videoId, toCommentId: childCommentId, text: text2 })
122
123 const text3 = 'my second answer to thread 1'
124 await command.addReply({ videoId, toCommentId: threadId, text: text3 })
125 })
126
127 it('Should get correctly the replies', async function () {
128 const tree = await command.getThread({ videoId: videoUUID, threadId })
129
130 expect(tree.comment.text).equal('my super first comment')
131 expect(tree.children).to.have.lengthOf(2)
132
133 const firstChild = tree.children[0]
134 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
135 expect(firstChild.children).to.have.lengthOf(1)
136
137 const childOfFirstChild = firstChild.children[0]
138 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
139 expect(childOfFirstChild.children).to.have.lengthOf(0)
140
141 const secondChild = tree.children[1]
142 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
143 expect(secondChild.children).to.have.lengthOf(0)
144
145 replyToDeleteId = secondChild.comment.id
146 })
147
148 it('Should create other threads', async function () {
149 const text1 = 'super thread 2'
150 await command.createThread({ videoId: videoUUID, text: text1 })
151
152 const text2 = 'super thread 3'
153 await command.createThread({ videoId: videoUUID, text: text2 })
154 })
155
156 it('Should list the threads', async function () {
157 const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' })
158
159 expect(body.total).to.equal(3)
160 expect(body.totalNotDeletedComments).to.equal(6)
161 expect(body.data).to.be.an('array')
162 expect(body.data).to.have.lengthOf(3)
163
164 expect(body.data[0].text).to.equal('my super first comment')
165 expect(body.data[0].totalReplies).to.equal(3)
166 expect(body.data[1].text).to.equal('super thread 2')
167 expect(body.data[1].totalReplies).to.equal(0)
168 expect(body.data[2].text).to.equal('super thread 3')
169 expect(body.data[2].totalReplies).to.equal(0)
170 })
171
172 it('Should list the and sort them by total replies', async function () {
173 const body = await command.listThreads({ videoId: videoUUID, sort: 'totalReplies' })
174
175 expect(body.data[2].text).to.equal('my super first comment')
176 expect(body.data[2].totalReplies).to.equal(3)
177 })
178
179 it('Should delete a reply', async function () {
180 await command.delete({ videoId, commentId: replyToDeleteId })
181
182 {
183 const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' })
184
185 expect(body.total).to.equal(3)
186 expect(body.totalNotDeletedComments).to.equal(5)
187 }
188
189 {
190 const tree = await command.getThread({ videoId: videoUUID, threadId })
191
192 expect(tree.comment.text).equal('my super first comment')
193 expect(tree.children).to.have.lengthOf(2)
194
195 const firstChild = tree.children[0]
196 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
197 expect(firstChild.children).to.have.lengthOf(1)
198
199 const childOfFirstChild = firstChild.children[0]
200 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
201 expect(childOfFirstChild.children).to.have.lengthOf(0)
202
203 const deletedChildOfFirstChild = tree.children[1]
204 expect(deletedChildOfFirstChild.comment.text).to.equal('')
205 expect(deletedChildOfFirstChild.comment.isDeleted).to.be.true
206 expect(deletedChildOfFirstChild.comment.deletedAt).to.not.be.null
207 expect(deletedChildOfFirstChild.comment.account).to.be.null
208 expect(deletedChildOfFirstChild.children).to.have.lengthOf(0)
209 }
210 })
211
212 it('Should delete a complete thread', async function () {
213 await command.delete({ videoId, commentId: threadId })
214
215 const body = await command.listThreads({ videoId: videoUUID, sort: 'createdAt' })
216 expect(body.total).to.equal(3)
217 expect(body.data).to.be.an('array')
218 expect(body.data).to.have.lengthOf(3)
219
220 expect(body.data[0].text).to.equal('')
221 expect(body.data[0].isDeleted).to.be.true
222 expect(body.data[0].deletedAt).to.not.be.null
223 expect(body.data[0].account).to.be.null
224 expect(body.data[0].totalReplies).to.equal(2)
225 expect(body.data[1].text).to.equal('super thread 2')
226 expect(body.data[1].totalReplies).to.equal(0)
227 expect(body.data[2].text).to.equal('super thread 3')
228 expect(body.data[2].totalReplies).to.equal(0)
229 })
230
231 it('Should count replies from the video author correctly', async function () {
232 await command.createThread({ videoId: videoUUID, text: 'my super first comment' })
233
234 const { data } = await command.listThreads({ videoId: videoUUID })
235 const threadId2 = data[0].threadId
236
237 const text2 = 'a first answer to thread 4 by a third party'
238 await command.addReply({ token: userAccessTokenServer1, videoId, toCommentId: threadId2, text: text2 })
239
240 const text3 = 'my second answer to thread 4'
241 await command.addReply({ videoId, toCommentId: threadId2, text: text3 })
242
243 const tree = await command.getThread({ videoId: videoUUID, threadId: threadId2 })
244 expect(tree.comment.totalRepliesFromVideoAuthor).to.equal(1)
245 expect(tree.comment.totalReplies).to.equal(2)
246 })
247 })
248
249 describe('All instance comments', function () {
250
251 it('Should list instance comments as admin', async function () {
252 {
253 const { data, total } = await command.listForAdmin({ start: 0, count: 1 })
254
255 expect(total).to.equal(7)
256 expect(data).to.have.lengthOf(1)
257 expect(data[0].text).to.equal('my second answer to thread 4')
258 expect(data[0].account.name).to.equal('root')
259 expect(data[0].account.displayName).to.equal('root')
260 expect(data[0].account.avatars).to.have.lengthOf(2)
261 }
262
263 {
264 const { data, total } = await command.listForAdmin({ start: 1, count: 2 })
265
266 expect(total).to.equal(7)
267 expect(data).to.have.lengthOf(2)
268
269 expect(data[0].account.avatars).to.have.lengthOf(2)
270 expect(data[1].account.avatars).to.have.lengthOf(2)
271 }
272 })
273
274 it('Should filter instance comments by isLocal', async function () {
275 const { total, data } = await command.listForAdmin({ isLocal: false })
276
277 expect(data).to.have.lengthOf(0)
278 expect(total).to.equal(0)
279 })
280
281 it('Should filter instance comments by onLocalVideo', async function () {
282 {
283 const { total, data } = await command.listForAdmin({ onLocalVideo: false })
284
285 expect(data).to.have.lengthOf(0)
286 expect(total).to.equal(0)
287 }
288
289 {
290 const { total, data } = await command.listForAdmin({ onLocalVideo: true })
291
292 expect(data).to.not.have.lengthOf(0)
293 expect(total).to.not.equal(0)
294 }
295 })
296
297 it('Should search instance comments by account', async function () {
298 const { total, data } = await command.listForAdmin({ searchAccount: 'user' })
299
300 expect(data).to.have.lengthOf(1)
301 expect(total).to.equal(1)
302
303 expect(data[0].text).to.equal('a first answer to thread 4 by a third party')
304 })
305
306 it('Should search instance comments by video', async function () {
307 {
308 const { total, data } = await command.listForAdmin({ searchVideo: 'video' })
309
310 expect(data).to.have.lengthOf(7)
311 expect(total).to.equal(7)
312 }
313
314 {
315 const { total, data } = await command.listForAdmin({ searchVideo: 'hello' })
316
317 expect(data).to.have.lengthOf(0)
318 expect(total).to.equal(0)
319 }
320 })
321
322 it('Should search instance comments', async function () {
323 const { total, data } = await command.listForAdmin({ search: 'super thread 3' })
324
325 expect(total).to.equal(1)
326
327 expect(data).to.have.lengthOf(1)
328 expect(data[0].text).to.equal('super thread 3')
329 })
330 })
331
332 after(async function () {
333 await cleanupTests([ server ])
334 })
335})
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts
deleted file mode 100644
index 1f3d4adbb..000000000
--- a/server/tests/api/videos/video-description.ts
+++ /dev/null
@@ -1,103 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import {
5 cleanupTests,
6 createMultipleServers,
7 doubleFollow,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 waitJobs
11} from '@shared/server-commands'
12
13describe('Test video description', function () {
14 let servers: PeerTubeServer[] = []
15 let videoUUID = ''
16 let videoId: number
17
18 const longDescription = 'my super description for server 1'.repeat(50)
19
20 // 30 characters * 6 -> 240 characters
21 const truncatedDescription = 'my super description for server 1'.repeat(7) + 'my super descrip...'
22
23 before(async function () {
24 this.timeout(40000)
25
26 // Run servers
27 servers = await createMultipleServers(2)
28
29 // Get the access tokens
30 await setAccessTokensToServers(servers)
31
32 // Server 1 and server 2 follow each other
33 await doubleFollow(servers[0], servers[1])
34 })
35
36 it('Should upload video with long description', async function () {
37 this.timeout(30000)
38
39 const attributes = {
40 description: longDescription
41 }
42 await servers[0].videos.upload({ attributes })
43
44 await waitJobs(servers)
45
46 const { data } = await servers[0].videos.list()
47
48 videoId = data[0].id
49 videoUUID = data[0].uuid
50 })
51
52 it('Should have a truncated description on each server when listing videos', async function () {
53 for (const server of servers) {
54 const { data } = await server.videos.list()
55 const video = data.find(v => v.uuid === videoUUID)
56
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)
68 }
69 })
70
71 it('Should fetch long description on each server', async function () {
72 for (const server of servers) {
73 const video = await server.videos.get({ id: videoUUID })
74
75 const { description } = await server.videos.getDescription({ descriptionPath: video.descriptionPath })
76 expect(description).to.equal(longDescription)
77 }
78 })
79
80 it('Should update with a short description', async function () {
81 const attributes = {
82 description: 'short description'
83 }
84 await servers[0].videos.update({ id: videoId, attributes })
85
86 await waitJobs(servers)
87 })
88
89 it('Should have a small description on each server', async function () {
90 for (const server of servers) {
91 const video = await server.videos.get({ id: videoUUID })
92
93 expect(video.description).to.equal('short description')
94
95 const { description } = await server.videos.getDescription({ descriptionPath: video.descriptionPath })
96 expect(description).to.equal('short description')
97 }
98 })
99
100 after(async function () {
101 await cleanupTests(servers)
102 })
103})
diff --git a/server/tests/api/videos/video-files.ts b/server/tests/api/videos/video-files.ts
deleted file mode 100644
index 4f75cd106..000000000
--- a/server/tests/api/videos/video-files.ts
+++ /dev/null
@@ -1,202 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { HttpStatusCode } from '@shared/models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 makeRawRequest,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
14
15describe('Test videos files', function () {
16 let servers: PeerTubeServer[]
17
18 // ---------------------------------------------------------------
19
20 before(async function () {
21 this.timeout(150_000)
22
23 servers = await createMultipleServers(2)
24 await setAccessTokensToServers(servers)
25
26 await doubleFollow(servers[0], servers[1])
27
28 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
29 })
30
31 describe('When deleting all files', function () {
32 let validId1: string
33 let validId2: string
34
35 before(async function () {
36 this.timeout(360_000)
37
38 {
39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1' })
40 validId1 = uuid
41 }
42
43 {
44 const { uuid } = await servers[0].videos.quickUpload({ name: 'video 2' })
45 validId2 = uuid
46 }
47
48 await waitJobs(servers)
49 })
50
51 it('Should delete web video files', async function () {
52 this.timeout(30_000)
53
54 await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1 })
55
56 await waitJobs(servers)
57
58 for (const server of servers) {
59 const video = await server.videos.get({ id: validId1 })
60
61 expect(video.files).to.have.lengthOf(0)
62 expect(video.streamingPlaylists).to.have.lengthOf(1)
63 }
64 })
65
66 it('Should delete HLS files', async function () {
67 this.timeout(30_000)
68
69 await servers[0].videos.removeHLSPlaylist({ videoId: validId2 })
70
71 await waitJobs(servers)
72
73 for (const server of servers) {
74 const video = await server.videos.get({ id: validId2 })
75
76 expect(video.files).to.have.length.above(0)
77 expect(video.streamingPlaylists).to.have.lengthOf(0)
78 }
79 })
80 })
81
82 describe('When deleting a specific file', function () {
83 let webVideoId: string
84 let hlsId: string
85
86 before(async function () {
87 this.timeout(120_000)
88
89 {
90 const { uuid } = await servers[0].videos.quickUpload({ name: 'web-video' })
91 webVideoId = uuid
92 }
93
94 {
95 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls' })
96 hlsId = uuid
97 }
98
99 await waitJobs(servers)
100 })
101
102 it('Shoulde delete a web video file', async function () {
103 this.timeout(30_000)
104
105 const video = await servers[0].videos.get({ id: webVideoId })
106 const files = video.files
107
108 await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: files[0].id })
109
110 await waitJobs(servers)
111
112 for (const server of servers) {
113 const video = await server.videos.get({ id: webVideoId })
114
115 expect(video.files).to.have.lengthOf(files.length - 1)
116 expect(video.files.find(f => f.id === files[0].id)).to.not.exist
117 }
118 })
119
120 it('Should delete all web video files', async function () {
121 this.timeout(30_000)
122
123 const video = await servers[0].videos.get({ id: webVideoId })
124 const files = video.files
125
126 for (const file of files) {
127 await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: file.id })
128 }
129
130 await waitJobs(servers)
131
132 for (const server of servers) {
133 const video = await server.videos.get({ id: webVideoId })
134
135 expect(video.files).to.have.lengthOf(0)
136 }
137 })
138
139 it('Should delete a hls file', async function () {
140 this.timeout(30_000)
141
142 const video = await servers[0].videos.get({ id: hlsId })
143 const files = video.streamingPlaylists[0].files
144 const toDelete = files[0]
145
146 await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: toDelete.id })
147
148 await waitJobs(servers)
149
150 for (const server of servers) {
151 const video = await server.videos.get({ id: hlsId })
152
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
155
156 const { text } = await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
157
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
160 }
161 })
162
163 it('Should delete all hls files', async function () {
164 this.timeout(30_000)
165
166 const video = await servers[0].videos.get({ id: hlsId })
167 const files = video.streamingPlaylists[0].files
168
169 for (const file of files) {
170 await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: file.id })
171 }
172
173 await waitJobs(servers)
174
175 for (const server of servers) {
176 const video = await server.videos.get({ id: hlsId })
177
178 expect(video.streamingPlaylists).to.have.lengthOf(0)
179 }
180 })
181
182 it('Should not delete last file of a video', async function () {
183 this.timeout(60_000)
184
185 const webVideoOnly = await servers[0].videos.get({ id: hlsId })
186 const hlsOnly = await servers[0].videos.get({ id: webVideoId })
187
188 for (let i = 0; i < 4; i++) {
189 await servers[0].videos.removeWebVideoFile({ videoId: webVideoOnly.id, fileId: webVideoOnly.files[i].id })
190 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[i].id })
191 }
192
193 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
194 await servers[0].videos.removeWebVideoFile({ videoId: webVideoOnly.id, fileId: webVideoOnly.files[4].id, expectedStatus })
195 await servers[0].videos.removeHLSFile({ videoId: hlsOnly.id, fileId: hlsOnly.streamingPlaylists[0].files[4].id, expectedStatus })
196 })
197 })
198
199 after(async function () {
200 await cleanupTests(servers)
201 })
202})
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
deleted file mode 100644
index b78b4f344..000000000
--- a/server/tests/api/videos/video-imports.ts
+++ /dev/null
@@ -1,631 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir, remove } from 'fs-extra'
5import { join } from 'path'
6import { FIXTURE_URLS, testCaptionFile, testImageGeneratedByFFmpeg } from '@server/tests/shared'
7import { areHttpImportTestsDisabled } from '@shared/core-utils'
8import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models'
9import {
10 cleanupTests,
11 createMultipleServers,
12 createSingleServer,
13 doubleFollow,
14 getServerImportConfig,
15 PeerTubeServer,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 waitJobs
19} from '@shared/server-commands'
20import { DeepPartial } from '@shared/typescript-utils'
21
22async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) {
23 const videoHttp = await server.videos.get({ id: idHttp })
24
25 expect(videoHttp.name).to.equal('small video - youtube')
26 expect(videoHttp.category.label).to.equal('News & Politics')
27 expect(videoHttp.licence.label).to.equal('Attribution')
28 expect(videoHttp.language.label).to.equal('Unknown')
29 expect(videoHttp.nsfw).to.be.false
30 expect(videoHttp.description).to.equal('this is a super description')
31 expect(videoHttp.tags).to.deep.equal([ 'tag1', 'tag2' ])
32 expect(videoHttp.files).to.have.lengthOf(1)
33
34 const originallyPublishedAt = new Date(videoHttp.originallyPublishedAt)
35 expect(originallyPublishedAt.getDate()).to.equal(14)
36 expect(originallyPublishedAt.getMonth()).to.equal(0)
37 expect(originallyPublishedAt.getFullYear()).to.equal(2019)
38
39 const videoMagnet = await server.videos.get({ id: idMagnet })
40 const videoTorrent = await server.videos.get({ id: idTorrent })
41
42 for (const video of [ videoMagnet, videoTorrent ]) {
43 expect(video.category.label).to.equal('Unknown')
44 expect(video.licence.label).to.equal('Unknown')
45 expect(video.language.label).to.equal('Unknown')
46 expect(video.nsfw).to.be.false
47 expect(video.description).to.equal('this is a super torrent description')
48 expect(video.tags).to.deep.equal([ 'tag_torrent1', 'tag_torrent2' ])
49 expect(video.files).to.have.lengthOf(1)
50 }
51
52 expect(videoTorrent.name).to.contain('你好 世界 720p.mp4')
53 expect(videoMagnet.name).to.contain('super peertube2 video')
54
55 const bodyCaptions = await server.captions.list({ videoId: idHttp })
56 expect(bodyCaptions.total).to.equal(2)
57}
58
59async function checkVideoServer2 (server: PeerTubeServer, id: number | string) {
60 const video = await server.videos.get({ id })
61
62 expect(video.name).to.equal('my super name')
63 expect(video.category.label).to.equal('Entertainment')
64 expect(video.licence.label).to.equal('Public Domain Dedication')
65 expect(video.language.label).to.equal('English')
66 expect(video.nsfw).to.be.false
67 expect(video.description).to.equal('my super description')
68 expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ])
69
70 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', video.thumbnailPath)
71
72 expect(video.files).to.have.lengthOf(1)
73
74 const bodyCaptions = await server.captions.list({ videoId: id })
75 expect(bodyCaptions.total).to.equal(2)
76}
77
78describe('Test video imports', function () {
79
80 if (areHttpImportTestsDisabled()) return
81
82 function runSuite (mode: 'youtube-dl' | 'yt-dlp') {
83
84 describe('Import ' + mode, function () {
85 let servers: PeerTubeServer[] = []
86
87 before(async function () {
88 this.timeout(60_000)
89
90 servers = await createMultipleServers(2, getServerImportConfig(mode))
91
92 await setAccessTokensToServers(servers)
93 await setDefaultVideoChannel(servers)
94
95 for (const server of servers) {
96 await server.config.updateExistingSubConfig({
97 newConfig: {
98 transcoding: {
99 alwaysTranscodeOriginalResolution: false
100 }
101 }
102 })
103 }
104
105 await doubleFollow(servers[0], servers[1])
106 })
107
108 it('Should import videos on server 1', async function () {
109 this.timeout(60_000)
110
111 const baseAttributes = {
112 channelId: servers[0].store.channel.id,
113 privacy: VideoPrivacy.PUBLIC
114 }
115
116 {
117 const attributes = { ...baseAttributes, targetUrl: FIXTURE_URLS.youtube }
118 const { video } = await servers[0].imports.importVideo({ attributes })
119 expect(video.name).to.equal('small video - youtube')
120
121 {
122 expect(video.thumbnailPath).to.match(new RegExp(`^/lazy-static/thumbnails/.+.jpg$`))
123 expect(video.previewPath).to.match(new RegExp(`^/lazy-static/previews/.+.jpg$`))
124
125 const suffix = mode === 'yt-dlp'
126 ? '_yt_dlp'
127 : ''
128
129 await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_thumbnail' + suffix, video.thumbnailPath)
130 await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_preview' + suffix, video.previewPath)
131 }
132
133 const bodyCaptions = await servers[0].captions.list({ videoId: video.id })
134 const videoCaptions = bodyCaptions.data
135 expect(videoCaptions).to.have.lengthOf(2)
136
137 {
138 const enCaption = videoCaptions.find(caption => caption.language.id === 'en')
139 expect(enCaption).to.exist
140 expect(enCaption.language.label).to.equal('English')
141 expect(enCaption.captionPath).to.match(new RegExp(`^/lazy-static/video-captions/.+-en.vtt$`))
142
143 const regex = `WEBVTT[ \n]+Kind: captions[ \n]+` +
144 `(Language: en[ \n]+)?` +
145 `00:00:01.600 --> 00:00:04.200( position:\\d+% line:\\d+%)?[ \n]+English \\(US\\)[ \n]+` +
146 `00:00:05.900 --> 00:00:07.999( position:\\d+% line:\\d+%)?[ \n]+This is a subtitle in American English[ \n]+` +
147 `00:00:10.000 --> 00:00:14.000( position:\\d+% line:\\d+%)?[ \n]+Adding subtitles is very easy to do`
148 await testCaptionFile(servers[0].url, enCaption.captionPath, new RegExp(regex))
149 }
150
151 {
152 const frCaption = videoCaptions.find(caption => caption.language.id === 'fr')
153 expect(frCaption).to.exist
154 expect(frCaption.language.label).to.equal('French')
155 expect(frCaption.captionPath).to.match(new RegExp(`^/lazy-static/video-captions/.+-fr.vtt`))
156
157 const regex = `WEBVTT[ \n]+Kind: captions[ \n]+` +
158 `(Language: fr[ \n]+)?` +
159 `00:00:01.600 --> 00:00:04.200( position:\\d+% line:\\d+%)?[ \n]+Français \\(FR\\)[ \n]+` +
160 `00:00:05.900 --> 00:00:07.999( position:\\d+% line:\\d+%)?[ \n]+C'est un sous-titre français[ \n]+` +
161 `00:00:10.000 --> 00:00:14.000( position:\\d+% line:\\d+%)?[ \n]+Ajouter un sous-titre est vraiment facile`
162
163 await testCaptionFile(servers[0].url, frCaption.captionPath, new RegExp(regex))
164 }
165 }
166
167 {
168 const attributes = {
169 ...baseAttributes,
170 magnetUri: FIXTURE_URLS.magnet,
171 description: 'this is a super torrent description',
172 tags: [ 'tag_torrent1', 'tag_torrent2' ]
173 }
174 const { video } = await servers[0].imports.importVideo({ attributes })
175 expect(video.name).to.equal('super peertube2 video')
176 }
177
178 {
179 const attributes = {
180 ...baseAttributes,
181 torrentfile: 'video-720p.torrent' as any,
182 description: 'this is a super torrent description',
183 tags: [ 'tag_torrent1', 'tag_torrent2' ]
184 }
185 const { video } = await servers[0].imports.importVideo({ attributes })
186 expect(video.name).to.equal('你好 世界 720p.mp4')
187 }
188 })
189
190 it('Should list the videos to import in my videos on server 1', async function () {
191 const { total, data } = await servers[0].videos.listMyVideos({ sort: 'createdAt' })
192
193 expect(total).to.equal(3)
194
195 expect(data).to.have.lengthOf(3)
196 expect(data[0].name).to.equal('small video - youtube')
197 expect(data[1].name).to.equal('super peertube2 video')
198 expect(data[2].name).to.equal('你好 世界 720p.mp4')
199 })
200
201 it('Should list the videos to import in my imports on server 1', async function () {
202 const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ sort: '-createdAt' })
203 expect(total).to.equal(3)
204
205 expect(videoImports).to.have.lengthOf(3)
206
207 expect(videoImports[2].targetUrl).to.equal(FIXTURE_URLS.youtube)
208 expect(videoImports[2].magnetUri).to.be.null
209 expect(videoImports[2].torrentName).to.be.null
210 expect(videoImports[2].video.name).to.equal('small video - youtube')
211
212 expect(videoImports[1].targetUrl).to.be.null
213 expect(videoImports[1].magnetUri).to.equal(FIXTURE_URLS.magnet)
214 expect(videoImports[1].torrentName).to.be.null
215 expect(videoImports[1].video.name).to.equal('super peertube2 video')
216
217 expect(videoImports[0].targetUrl).to.be.null
218 expect(videoImports[0].magnetUri).to.be.null
219 expect(videoImports[0].torrentName).to.equal('video-720p.torrent')
220 expect(videoImports[0].video.name).to.equal('你好 世界 720p.mp4')
221 })
222
223 it('Should filter my imports on target URL', async function () {
224 const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ targetUrl: FIXTURE_URLS.youtube })
225 expect(total).to.equal(1)
226 expect(videoImports).to.have.lengthOf(1)
227
228 expect(videoImports[0].targetUrl).to.equal(FIXTURE_URLS.youtube)
229 })
230
231 it('Should search in my imports', async function () {
232 const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ search: 'peertube2' })
233 expect(total).to.equal(1)
234 expect(videoImports).to.have.lengthOf(1)
235
236 expect(videoImports[0].magnetUri).to.equal(FIXTURE_URLS.magnet)
237 expect(videoImports[0].video.name).to.equal('super peertube2 video')
238 })
239
240 it('Should have the video listed on the two instances', async function () {
241 this.timeout(120_000)
242
243 await waitJobs(servers)
244
245 for (const server of servers) {
246 const { total, data } = await server.videos.list()
247 expect(total).to.equal(3)
248 expect(data).to.have.lengthOf(3)
249
250 const [ videoHttp, videoMagnet, videoTorrent ] = data
251 await checkVideosServer1(server, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid)
252 }
253 })
254
255 it('Should import a video on server 2 with some fields', async function () {
256 this.timeout(60_000)
257
258 const { video } = await servers[1].imports.importVideo({
259 attributes: {
260 targetUrl: FIXTURE_URLS.youtube,
261 channelId: servers[1].store.channel.id,
262 privacy: VideoPrivacy.PUBLIC,
263 category: 10,
264 licence: 7,
265 language: 'en',
266 name: 'my super name',
267 description: 'my super description',
268 tags: [ 'supertag1', 'supertag2' ],
269 thumbnailfile: 'custom-thumbnail.jpg'
270 }
271 })
272 expect(video.name).to.equal('my super name')
273 })
274
275 it('Should have the videos listed on the two instances', async function () {
276 this.timeout(120_000)
277
278 await waitJobs(servers)
279
280 for (const server of servers) {
281 const { total, data } = await server.videos.list()
282 expect(total).to.equal(4)
283 expect(data).to.have.lengthOf(4)
284
285 await checkVideoServer2(server, data[0].uuid)
286
287 const [ , videoHttp, videoMagnet, videoTorrent ] = data
288 await checkVideosServer1(server, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid)
289 }
290 })
291
292 it('Should import a video that will be transcoded', async function () {
293 this.timeout(240_000)
294
295 const attributes = {
296 name: 'transcoded video',
297 magnetUri: FIXTURE_URLS.magnet,
298 channelId: servers[1].store.channel.id,
299 privacy: VideoPrivacy.PUBLIC
300 }
301 const { video } = await servers[1].imports.importVideo({ attributes })
302 const videoUUID = video.uuid
303
304 await waitJobs(servers)
305
306 for (const server of servers) {
307 const video = await server.videos.get({ id: videoUUID })
308
309 expect(video.name).to.equal('transcoded video')
310 expect(video.files).to.have.lengthOf(4)
311 }
312 })
313
314 it('Should import no HDR version on a HDR video', async function () {
315 this.timeout(300_000)
316
317 const config: DeepPartial<CustomConfig> = {
318 transcoding: {
319 enabled: true,
320 resolutions: {
321 '0p': false,
322 '144p': true,
323 '240p': true,
324 '360p': false,
325 '480p': false,
326 '720p': false,
327 '1080p': false, // the resulting resolution shouldn't be higher than this, and not vp9.2/av01
328 '1440p': false,
329 '2160p': false
330 },
331 webVideos: { enabled: true },
332 hls: { enabled: false }
333 }
334 }
335 await servers[0].config.updateExistingSubConfig({ newConfig: config })
336
337 const attributes = {
338 name: 'hdr video',
339 targetUrl: FIXTURE_URLS.youtubeHDR,
340 channelId: servers[0].store.channel.id,
341 privacy: VideoPrivacy.PUBLIC
342 }
343 const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
344 const videoUUID = videoImported.uuid
345
346 await waitJobs(servers)
347
348 // test resolution
349 const video = await servers[0].videos.get({ id: videoUUID })
350 expect(video.name).to.equal('hdr video')
351 const maxResolution = Math.max.apply(Math, video.files.map(function (o) { return o.resolution.id }))
352 expect(maxResolution, 'expected max resolution not met').to.equals(VideoResolution.H_240P)
353 })
354
355 it('Should not import resolution higher than enabled transcoding resolution', async function () {
356 this.timeout(300_000)
357
358 const config: DeepPartial<CustomConfig> = {
359 transcoding: {
360 enabled: true,
361 resolutions: {
362 '0p': false,
363 '144p': true,
364 '240p': false,
365 '360p': false,
366 '480p': false,
367 '720p': false,
368 '1080p': false,
369 '1440p': false,
370 '2160p': false
371 },
372 alwaysTranscodeOriginalResolution: false
373 }
374 }
375 await servers[0].config.updateExistingSubConfig({ newConfig: config })
376
377 const attributes = {
378 name: 'small resolution video',
379 targetUrl: FIXTURE_URLS.youtube,
380 channelId: servers[0].store.channel.id,
381 privacy: VideoPrivacy.PUBLIC
382 }
383 const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
384 const videoUUID = videoImported.uuid
385
386 await waitJobs(servers)
387
388 // test resolution
389 const video = await servers[0].videos.get({ id: videoUUID })
390 expect(video.name).to.equal('small resolution video')
391 expect(video.files).to.have.lengthOf(1)
392 expect(video.files[0].resolution.id).to.equal(144)
393 })
394
395 it('Should import resolution higher than enabled transcoding resolution', async function () {
396 this.timeout(300_000)
397
398 const config: DeepPartial<CustomConfig> = {
399 transcoding: {
400 alwaysTranscodeOriginalResolution: true
401 }
402 }
403 await servers[0].config.updateExistingSubConfig({ newConfig: config })
404
405 const attributes = {
406 name: 'bigger resolution video',
407 targetUrl: FIXTURE_URLS.youtube,
408 channelId: servers[0].store.channel.id,
409 privacy: VideoPrivacy.PUBLIC
410 }
411 const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
412 const videoUUID = videoImported.uuid
413
414 await waitJobs(servers)
415
416 // test resolution
417 const video = await servers[0].videos.get({ id: videoUUID })
418 expect(video.name).to.equal('bigger resolution video')
419
420 expect(video.files).to.have.lengthOf(2)
421 expect(video.files.find(f => f.resolution.id === 240)).to.exist
422 expect(video.files.find(f => f.resolution.id === 144)).to.exist
423 })
424
425 it('Should import a peertube video', async function () {
426 this.timeout(120_000)
427
428 const toTest = [ FIXTURE_URLS.peertube_long ]
429
430 // TODO: include peertube_short when https://github.com/ytdl-org/youtube-dl/pull/29475 is merged
431 if (mode === 'yt-dlp') {
432 toTest.push(FIXTURE_URLS.peertube_short)
433 }
434
435 for (const targetUrl of toTest) {
436 await servers[0].config.disableTranscoding()
437
438 const attributes = {
439 targetUrl,
440 channelId: servers[0].store.channel.id,
441 privacy: VideoPrivacy.PUBLIC
442 }
443 const { video } = await servers[0].imports.importVideo({ attributes })
444 const videoUUID = video.uuid
445
446 await waitJobs(servers)
447
448 for (const server of servers) {
449 const video = await server.videos.get({ id: videoUUID })
450
451 expect(video.name).to.equal('E2E tests')
452
453 const { data: captions } = await server.captions.list({ videoId: videoUUID })
454 expect(captions).to.have.lengthOf(1)
455 expect(captions[0].language.id).to.equal('fr')
456
457 const str = `WEBVTT FILE\r?\n\r?\n` +
458 `1\r?\n` +
459 `00:00:04.000 --> 00:00:09.000\r?\n` +
460 `January 1, 1994. The North American`
461 await testCaptionFile(server.url, captions[0].captionPath, new RegExp(str))
462 }
463 }
464 })
465
466 after(async function () {
467 await cleanupTests(servers)
468 })
469 })
470 }
471
472 // FIXME: youtube-dl seems broken
473 // runSuite('youtube-dl')
474
475 runSuite('yt-dlp')
476
477 describe('Delete/cancel an import', function () {
478 let server: PeerTubeServer
479
480 let finishedImportId: number
481 let finishedVideo: Video
482 let pendingImportId: number
483
484 async function importVideo (name: string) {
485 const attributes = { name, channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
486 const res = await server.imports.importVideo({ attributes })
487
488 return res.id
489 }
490
491 before(async function () {
492 this.timeout(120_000)
493
494 server = await createSingleServer(1)
495
496 await setAccessTokensToServers([ server ])
497 await setDefaultVideoChannel([ server ])
498
499 finishedImportId = await importVideo('finished')
500 await waitJobs([ server ])
501
502 await server.jobs.pauseJobQueue()
503 pendingImportId = await importVideo('pending')
504
505 const { data } = await server.imports.getMyVideoImports()
506 expect(data).to.have.lengthOf(2)
507
508 finishedVideo = data.find(i => i.id === finishedImportId).video
509 })
510
511 it('Should delete a video import', async function () {
512 await server.imports.delete({ importId: finishedImportId })
513
514 const { data } = await server.imports.getMyVideoImports()
515 expect(data).to.have.lengthOf(1)
516 expect(data[0].id).to.equal(pendingImportId)
517 expect(data[0].state.id).to.equal(VideoImportState.PENDING)
518 })
519
520 it('Should not have deleted the associated video', async function () {
521 const video = await server.videos.get({ id: finishedVideo.id, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
522 expect(video.name).to.equal('finished')
523 expect(video.state.id).to.equal(VideoState.PUBLISHED)
524 })
525
526 it('Should cancel a video import', async function () {
527 await server.imports.cancel({ importId: pendingImportId })
528
529 const { data } = await server.imports.getMyVideoImports()
530 expect(data).to.have.lengthOf(1)
531 expect(data[0].id).to.equal(pendingImportId)
532 expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
533 })
534
535 it('Should not have processed the cancelled video import', async function () {
536 this.timeout(60_000)
537
538 await server.jobs.resumeJobQueue()
539
540 await waitJobs([ server ])
541
542 const { data } = await server.imports.getMyVideoImports()
543 expect(data).to.have.lengthOf(1)
544 expect(data[0].id).to.equal(pendingImportId)
545 expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
546 expect(data[0].video.state.id).to.equal(VideoState.TO_IMPORT)
547 })
548
549 it('Should delete the cancelled video import', async function () {
550 await server.imports.delete({ importId: pendingImportId })
551 const { data } = await server.imports.getMyVideoImports()
552 expect(data).to.have.lengthOf(0)
553 })
554
555 after(async function () {
556 await cleanupTests([ server ])
557 })
558 })
559
560 describe('Auto update', function () {
561 let server: PeerTubeServer
562
563 function quickPeerTubeImport () {
564 const attributes = {
565 targetUrl: FIXTURE_URLS.peertube_long,
566 channelId: server.store.channel.id,
567 privacy: VideoPrivacy.PUBLIC
568 }
569
570 return server.imports.importVideo({ attributes })
571 }
572
573 async function testBinaryUpdate (releaseUrl: string, releaseName: string) {
574 await remove(join(server.servers.buildDirectory('bin'), releaseName))
575
576 await server.kill()
577 await server.run({
578 import: {
579 videos: {
580 http: {
581 youtube_dl_release: {
582 url: releaseUrl,
583 name: releaseName
584 }
585 }
586 }
587 }
588 })
589
590 await quickPeerTubeImport()
591
592 const base = server.servers.buildDirectory('bin')
593 const content = await readdir(base)
594 const binaryPath = join(base, releaseName)
595
596 expect(await pathExists(binaryPath), `${binaryPath} does not exist in ${base} (${content.join(', ')})`).to.be.true
597 }
598
599 before(async function () {
600 this.timeout(30_000)
601
602 // Run servers
603 server = await createSingleServer(1)
604
605 await setAccessTokensToServers([ server ])
606 await setDefaultVideoChannel([ server ])
607 })
608
609 it('Should update youtube-dl from github URL', async function () {
610 this.timeout(120_000)
611
612 await testBinaryUpdate('https://api.github.com/repos/ytdl-org/youtube-dl/releases', 'youtube-dl')
613 })
614
615 it('Should update youtube-dl from raw URL', async function () {
616 this.timeout(120_000)
617
618 await testBinaryUpdate('https://yt-dl.org/downloads/latest/youtube-dl', 'youtube-dl')
619 })
620
621 it('Should update youtube-dl from youtube-dl fork', async function () {
622 this.timeout(120_000)
623
624 await testBinaryUpdate('https://api.github.com/repos/yt-dlp/yt-dlp/releases', 'yt-dlp')
625 })
626
627 after(async function () {
628 await cleanupTests([ server ])
629 })
630 })
631})
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts
deleted file mode 100644
index 65e9c8730..000000000
--- a/server/tests/api/videos/video-nsfw.ts
+++ /dev/null
@@ -1,227 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5import { BooleanBothQuery, CustomConfig, ResultList, Video, VideosOverview } from '@shared/models'
6
7function createOverviewRes (overview: VideosOverview) {
8 const videos = overview.categories[0].videos
9 return { data: videos, total: videos.length }
10}
11
12describe('Test video NSFW policy', function () {
13 let server: PeerTubeServer
14 let userAccessToken: string
15 let customConfig: CustomConfig
16
17 async function getVideosFunctions (token?: string, query: { nsfw?: BooleanBothQuery } = {}) {
18 const user = await server.users.getMyInfo()
19
20 const channelName = user.videoChannels[0].name
21 const accountName = user.account.name + '@' + user.account.host
22
23 const hasQuery = Object.keys(query).length !== 0
24 let promises: Promise<ResultList<Video>>[]
25
26 if (token) {
27 promises = [
28 server.search.advancedVideoSearch({ token, search: { search: 'n', sort: '-publishedAt', ...query } }),
29 server.videos.listWithToken({ token, ...query }),
30 server.videos.listByAccount({ token, handle: accountName, ...query }),
31 server.videos.listByChannel({ token, handle: channelName, ...query })
32 ]
33
34 // Overviews do not support video filters
35 if (!hasQuery) {
36 const p = server.overviews.getVideos({ page: 1, token })
37 .then(res => createOverviewRes(res))
38 promises.push(p)
39 }
40
41 return Promise.all(promises)
42 }
43
44 promises = [
45 server.search.searchVideos({ search: 'n', sort: '-publishedAt' }),
46 server.videos.list(),
47 server.videos.listByAccount({ token: null, handle: accountName }),
48 server.videos.listByChannel({ token: null, handle: channelName })
49 ]
50
51 // Overviews do not support video filters
52 if (!hasQuery) {
53 const p = server.overviews.getVideos({ page: 1 })
54 .then(res => createOverviewRes(res))
55 promises.push(p)
56 }
57
58 return Promise.all(promises)
59 }
60
61 before(async function () {
62 this.timeout(50000)
63 server = await createSingleServer(1)
64
65 // Get the access tokens
66 await setAccessTokensToServers([ server ])
67
68 {
69 const attributes = { name: 'nsfw', nsfw: true, category: 1 }
70 await server.videos.upload({ attributes })
71 }
72
73 {
74 const attributes = { name: 'normal', nsfw: false, category: 1 }
75 await server.videos.upload({ attributes })
76 }
77
78 customConfig = await server.config.getCustomConfig()
79 })
80
81 describe('Instance default NSFW policy', function () {
82
83 it('Should display NSFW videos with display default NSFW policy', async function () {
84 const serverConfig = await server.config.getConfig()
85 expect(serverConfig.instance.defaultNSFWPolicy).to.equal('display')
86
87 for (const body of await getVideosFunctions()) {
88 expect(body.total).to.equal(2)
89
90 const videos = body.data
91 expect(videos).to.have.lengthOf(2)
92 expect(videos[0].name).to.equal('normal')
93 expect(videos[1].name).to.equal('nsfw')
94 }
95 })
96
97 it('Should not display NSFW videos with do_not_list default NSFW policy', async function () {
98 customConfig.instance.defaultNSFWPolicy = 'do_not_list'
99 await server.config.updateCustomConfig({ newCustomConfig: customConfig })
100
101 const serverConfig = await server.config.getConfig()
102 expect(serverConfig.instance.defaultNSFWPolicy).to.equal('do_not_list')
103
104 for (const body of await getVideosFunctions()) {
105 expect(body.total).to.equal(1)
106
107 const videos = body.data
108 expect(videos).to.have.lengthOf(1)
109 expect(videos[0].name).to.equal('normal')
110 }
111 })
112
113 it('Should display NSFW videos with blur default NSFW policy', async function () {
114 customConfig.instance.defaultNSFWPolicy = 'blur'
115 await server.config.updateCustomConfig({ newCustomConfig: customConfig })
116
117 const serverConfig = await server.config.getConfig()
118 expect(serverConfig.instance.defaultNSFWPolicy).to.equal('blur')
119
120 for (const body of await getVideosFunctions()) {
121 expect(body.total).to.equal(2)
122
123 const videos = body.data
124 expect(videos).to.have.lengthOf(2)
125 expect(videos[0].name).to.equal('normal')
126 expect(videos[1].name).to.equal('nsfw')
127 }
128 })
129 })
130
131 describe('User NSFW policy', function () {
132
133 it('Should create a user having the default nsfw policy', async function () {
134 const username = 'user1'
135 const password = 'my super password'
136 await server.users.create({ username, password })
137
138 userAccessToken = await server.login.getAccessToken({ username, password })
139
140 const user = await server.users.getMyInfo({ token: userAccessToken })
141 expect(user.nsfwPolicy).to.equal('blur')
142 })
143
144 it('Should display NSFW videos with blur user NSFW policy', async function () {
145 customConfig.instance.defaultNSFWPolicy = 'do_not_list'
146 await server.config.updateCustomConfig({ newCustomConfig: customConfig })
147
148 for (const body of await getVideosFunctions(userAccessToken)) {
149 expect(body.total).to.equal(2)
150
151 const videos = body.data
152 expect(videos).to.have.lengthOf(2)
153 expect(videos[0].name).to.equal('normal')
154 expect(videos[1].name).to.equal('nsfw')
155 }
156 })
157
158 it('Should display NSFW videos with display user NSFW policy', async function () {
159 await server.users.updateMe({ nsfwPolicy: 'display' })
160
161 for (const body of await getVideosFunctions(server.accessToken)) {
162 expect(body.total).to.equal(2)
163
164 const videos = body.data
165 expect(videos).to.have.lengthOf(2)
166 expect(videos[0].name).to.equal('normal')
167 expect(videos[1].name).to.equal('nsfw')
168 }
169 })
170
171 it('Should not display NSFW videos with do_not_list user NSFW policy', async function () {
172 await server.users.updateMe({ nsfwPolicy: 'do_not_list' })
173
174 for (const body of await getVideosFunctions(server.accessToken)) {
175 expect(body.total).to.equal(1)
176
177 const videos = body.data
178 expect(videos).to.have.lengthOf(1)
179 expect(videos[0].name).to.equal('normal')
180 }
181 })
182
183 it('Should be able to see my NSFW videos even with do_not_list user NSFW policy', async function () {
184 const { total, data } = await server.videos.listMyVideos()
185 expect(total).to.equal(2)
186
187 expect(data).to.have.lengthOf(2)
188 expect(data[0].name).to.equal('normal')
189 expect(data[1].name).to.equal('nsfw')
190 })
191
192 it('Should display NSFW videos when the nsfw param === true', async function () {
193 for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'true' })) {
194 expect(body.total).to.equal(1)
195
196 const videos = body.data
197 expect(videos).to.have.lengthOf(1)
198 expect(videos[0].name).to.equal('nsfw')
199 }
200 })
201
202 it('Should hide NSFW videos when the nsfw param === true', async function () {
203 for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'false' })) {
204 expect(body.total).to.equal(1)
205
206 const videos = body.data
207 expect(videos).to.have.lengthOf(1)
208 expect(videos[0].name).to.equal('normal')
209 }
210 })
211
212 it('Should display both videos when the nsfw param === both', async function () {
213 for (const body of await getVideosFunctions(server.accessToken, { nsfw: 'both' })) {
214 expect(body.total).to.equal(2)
215
216 const videos = body.data
217 expect(videos).to.have.lengthOf(2)
218 expect(videos[0].name).to.equal('normal')
219 expect(videos[1].name).to.equal('nsfw')
220 }
221 })
222 })
223
224 after(async function () {
225 await cleanupTests([ server ])
226 })
227})
diff --git a/server/tests/api/videos/video-passwords.ts b/server/tests/api/videos/video-passwords.ts
deleted file mode 100644
index e01a93a4d..000000000
--- a/server/tests/api/videos/video-passwords.ts
+++ /dev/null
@@ -1,97 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import {
5 cleanupTests,
6 createSingleServer,
7 VideoPasswordsCommand,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 setDefaultAccountAvatar,
11 setDefaultChannelAvatar
12} from '@shared/server-commands'
13import { VideoPrivacy } from '@shared/models'
14
15describe('Test video passwords', function () {
16 let server: PeerTubeServer
17 let videoUUID: string
18
19 let userAccessTokenServer1: string
20
21 let videoPasswords: string[] = []
22 let command: VideoPasswordsCommand
23
24 before(async function () {
25 this.timeout(30000)
26
27 server = await createSingleServer(1)
28
29 await setAccessTokensToServers([ server ])
30
31 for (let i = 0; i < 10; i++) {
32 videoPasswords.push(`password ${i + 1}`)
33 }
34 const { uuid } = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PASSWORD_PROTECTED, videoPasswords } })
35 videoUUID = uuid
36
37 await setDefaultChannelAvatar(server)
38 await setDefaultAccountAvatar(server)
39
40 userAccessTokenServer1 = await server.users.generateUserAndToken('user1')
41 await setDefaultChannelAvatar(server, 'user1_channel')
42 await setDefaultAccountAvatar(server, userAccessTokenServer1)
43
44 command = server.videoPasswords
45 })
46
47 it('Should list video passwords', async function () {
48 const body = await command.list({ videoId: videoUUID })
49
50 expect(body.total).to.equal(10)
51 expect(body.data).to.be.an('array')
52 expect(body.data).to.have.lengthOf(10)
53 })
54
55 it('Should filter passwords on this video', async function () {
56 const body = await command.list({ videoId: videoUUID, count: 2, start: 3, sort: 'createdAt' })
57
58 expect(body.total).to.equal(10)
59 expect(body.data).to.be.an('array')
60 expect(body.data).to.have.lengthOf(2)
61 expect(body.data[0].password).to.equal('password 4')
62 expect(body.data[1].password).to.equal('password 5')
63 })
64
65 it('Should update password for this video', async function () {
66 videoPasswords = [ 'my super new password 1', 'my super new password 2' ]
67
68 await command.updateAll({ videoId: videoUUID, passwords: videoPasswords })
69 const body = await command.list({ videoId: videoUUID })
70 expect(body.total).to.equal(2)
71 expect(body.data).to.be.an('array')
72 expect(body.data).to.have.lengthOf(2)
73 expect(body.data[0].password).to.equal('my super new password 2')
74 expect(body.data[1].password).to.equal('my super new password 1')
75 })
76
77 it('Should delete one password', async function () {
78 {
79 const body = await command.list({ videoId: videoUUID })
80 expect(body.total).to.equal(2)
81 expect(body.data).to.be.an('array')
82 expect(body.data).to.have.lengthOf(2)
83 await command.remove({ id: body.data[0].id, videoId: videoUUID })
84 }
85 {
86 const body = await command.list({ videoId: videoUUID })
87
88 expect(body.total).to.equal(1)
89 expect(body.data).to.be.an('array')
90 expect(body.data).to.have.lengthOf(1)
91 }
92 })
93
94 after(async function () {
95 await cleanupTests([ server ])
96 })
97})
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
deleted file mode 100644
index c274c20bf..000000000
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ /dev/null
@@ -1,234 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { VideoPlaylistPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 waitJobs
14} from '@shared/server-commands'
15
16describe('Playlist thumbnail', function () {
17 let servers: PeerTubeServer[] = []
18
19 let playlistWithoutThumbnailId: number
20 let playlistWithThumbnailId: number
21
22 let withThumbnailE1: number
23 let withThumbnailE2: number
24 let withoutThumbnailE1: number
25 let withoutThumbnailE2: number
26
27 let video1: number
28 let video2: number
29
30 async function getPlaylistWithoutThumbnail (server: PeerTubeServer) {
31 const body = await server.playlists.list({ start: 0, count: 10 })
32
33 return body.data.find(p => p.displayName === 'playlist without thumbnail')
34 }
35
36 async function getPlaylistWithThumbnail (server: PeerTubeServer) {
37 const body = await server.playlists.list({ start: 0, count: 10 })
38
39 return body.data.find(p => p.displayName === 'playlist with thumbnail')
40 }
41
42 before(async function () {
43 this.timeout(120000)
44
45 servers = await createMultipleServers(2)
46
47 // Get the access tokens
48 await setAccessTokensToServers(servers)
49 await setDefaultVideoChannel(servers)
50
51 for (const server of servers) {
52 await server.config.disableTranscoding()
53 }
54
55 // Server 1 and server 2 follow each other
56 await doubleFollow(servers[0], servers[1])
57
58 video1 = (await servers[0].videos.quickUpload({ name: 'video 1' })).id
59 video2 = (await servers[0].videos.quickUpload({ name: 'video 2' })).id
60
61 await waitJobs(servers)
62 })
63
64 it('Should automatically update the thumbnail when adding an element', async function () {
65 this.timeout(30000)
66
67 const created = await servers[1].playlists.create({
68 attributes: {
69 displayName: 'playlist without thumbnail',
70 privacy: VideoPlaylistPrivacy.PUBLIC,
71 videoChannelId: servers[1].store.channel.id
72 }
73 })
74 playlistWithoutThumbnailId = created.id
75
76 const added = await servers[1].playlists.addElement({
77 playlistId: playlistWithoutThumbnailId,
78 attributes: { videoId: video1 }
79 })
80 withoutThumbnailE1 = added.id
81
82 await waitJobs(servers)
83
84 for (const server of servers) {
85 const p = await getPlaylistWithoutThumbnail(server)
86 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
87 }
88 })
89
90 it('Should not update the thumbnail if we explicitly uploaded a thumbnail', async function () {
91 this.timeout(30000)
92
93 const created = await servers[1].playlists.create({
94 attributes: {
95 displayName: 'playlist with thumbnail',
96 privacy: VideoPlaylistPrivacy.PUBLIC,
97 videoChannelId: servers[1].store.channel.id,
98 thumbnailfile: 'custom-thumbnail.jpg'
99 }
100 })
101 playlistWithThumbnailId = created.id
102
103 const added = await servers[1].playlists.addElement({
104 playlistId: playlistWithThumbnailId,
105 attributes: { videoId: video1 }
106 })
107 withThumbnailE1 = added.id
108
109 await waitJobs(servers)
110
111 for (const server of servers) {
112 const p = await getPlaylistWithThumbnail(server)
113 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
114 }
115 })
116
117 it('Should automatically update the thumbnail when moving the first element', async function () {
118 this.timeout(30000)
119
120 const added = await servers[1].playlists.addElement({
121 playlistId: playlistWithoutThumbnailId,
122 attributes: { videoId: video2 }
123 })
124 withoutThumbnailE2 = added.id
125
126 await servers[1].playlists.reorderElements({
127 playlistId: playlistWithoutThumbnailId,
128 attributes: {
129 startPosition: 1,
130 insertAfterPosition: 2
131 }
132 })
133
134 await waitJobs(servers)
135
136 for (const server of servers) {
137 const p = await getPlaylistWithoutThumbnail(server)
138 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
139 }
140 })
141
142 it('Should not update the thumbnail when moving the first element if we explicitly uploaded a thumbnail', async function () {
143 this.timeout(30000)
144
145 const added = await servers[1].playlists.addElement({
146 playlistId: playlistWithThumbnailId,
147 attributes: { videoId: video2 }
148 })
149 withThumbnailE2 = added.id
150
151 await servers[1].playlists.reorderElements({
152 playlistId: playlistWithThumbnailId,
153 attributes: {
154 startPosition: 1,
155 insertAfterPosition: 2
156 }
157 })
158
159 await waitJobs(servers)
160
161 for (const server of servers) {
162 const p = await getPlaylistWithThumbnail(server)
163 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
164 }
165 })
166
167 it('Should automatically update the thumbnail when deleting the first element', async function () {
168 this.timeout(30000)
169
170 await servers[1].playlists.removeElement({
171 playlistId: playlistWithoutThumbnailId,
172 elementId: withoutThumbnailE1
173 })
174
175 await waitJobs(servers)
176
177 for (const server of servers) {
178 const p = await getPlaylistWithoutThumbnail(server)
179 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath)
180 }
181 })
182
183 it('Should not update the thumbnail when deleting the first element if we explicitly uploaded a thumbnail', async function () {
184 this.timeout(30000)
185
186 await servers[1].playlists.removeElement({
187 playlistId: playlistWithThumbnailId,
188 elementId: withThumbnailE1
189 })
190
191 await waitJobs(servers)
192
193 for (const server of servers) {
194 const p = await getPlaylistWithThumbnail(server)
195 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
196 }
197 })
198
199 it('Should the thumbnail when we delete the last element', async function () {
200 this.timeout(30000)
201
202 await servers[1].playlists.removeElement({
203 playlistId: playlistWithoutThumbnailId,
204 elementId: withoutThumbnailE2
205 })
206
207 await waitJobs(servers)
208
209 for (const server of servers) {
210 const p = await getPlaylistWithoutThumbnail(server)
211 expect(p.thumbnailPath).to.be.null
212 }
213 })
214
215 it('Should not update the thumbnail when we delete the last element if we explicitly uploaded a thumbnail', async function () {
216 this.timeout(30000)
217
218 await servers[1].playlists.removeElement({
219 playlistId: playlistWithThumbnailId,
220 elementId: withThumbnailE2
221 })
222
223 await waitJobs(servers)
224
225 for (const server of servers) {
226 const p = await getPlaylistWithThumbnail(server)
227 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath)
228 }
229 })
230
231 after(async function () {
232 await cleanupTests(servers)
233 })
234})
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
deleted file mode 100644
index 3bfa874cb..000000000
--- a/server/tests/api/videos/video-playlists.ts
+++ /dev/null
@@ -1,1208 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkPlaylistFilesWereRemoved, testImageGeneratedByFFmpeg } from '@server/tests/shared'
5import { wait } from '@shared/core-utils'
6import { uuidToShort } from '@shared/extra-utils'
7import {
8 HttpStatusCode,
9 VideoPlaylist,
10 VideoPlaylistCreateResult,
11 VideoPlaylistElementType,
12 VideoPlaylistPrivacy,
13 VideoPlaylistType,
14 VideoPrivacy
15} from '@shared/models'
16import {
17 cleanupTests,
18 createMultipleServers,
19 doubleFollow,
20 PeerTubeServer,
21 PlaylistsCommand,
22 setAccessTokensToServers,
23 setDefaultAccountAvatar,
24 setDefaultVideoChannel,
25 waitJobs
26} from '@shared/server-commands'
27
28async function checkPlaylistElementType (
29 servers: PeerTubeServer[],
30 playlistId: string,
31 type: VideoPlaylistElementType,
32 position: number,
33 name: string,
34 total: number
35) {
36 for (const server of servers) {
37 const body = await server.playlists.listVideos({ token: server.accessToken, playlistId, start: 0, count: 10 })
38 expect(body.total).to.equal(total)
39
40 const videoElement = body.data.find(e => e.position === position)
41 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
42
43 if (type === VideoPlaylistElementType.REGULAR) {
44 expect(videoElement.video).to.not.be.null
45 expect(videoElement.video.name).to.equal(name)
46 } else {
47 expect(videoElement.video).to.be.null
48 }
49 }
50}
51
52describe('Test video playlists', function () {
53 let servers: PeerTubeServer[] = []
54
55 let playlistServer2Id1: number
56 let playlistServer2Id2: number
57 let playlistServer2UUID2: string
58
59 let playlistServer1Id: number
60 let playlistServer1DisplayName: string
61 let playlistServer1UUID: string
62 let playlistServer1UUID2: string
63
64 let playlistElementServer1Video4: number
65 let playlistElementServer1Video5: number
66 let playlistElementNSFW: number
67
68 let nsfwVideoServer1: number
69
70 let userTokenServer1: string
71
72 let commands: PlaylistsCommand[]
73
74 before(async function () {
75 this.timeout(240000)
76
77 servers = await createMultipleServers(3)
78
79 // Get the access tokens
80 await setAccessTokensToServers(servers)
81 await setDefaultVideoChannel(servers)
82 await setDefaultAccountAvatar(servers)
83
84 for (const server of servers) {
85 await server.config.disableTranscoding()
86 }
87
88 // Server 1 and server 2 follow each other
89 await doubleFollow(servers[0], servers[1])
90 // Server 1 and server 3 follow each other
91 await doubleFollow(servers[0], servers[2])
92
93 commands = servers.map(s => s.playlists)
94
95 {
96 servers[0].store.videos = []
97 servers[1].store.videos = []
98 servers[2].store.videos = []
99
100 for (const server of servers) {
101 for (let i = 0; i < 7; i++) {
102 const name = `video ${i} server ${server.serverNumber}`
103 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
104
105 server.store.videos.push(video)
106 }
107 }
108 }
109
110 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
111
112 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
113
114 await waitJobs(servers)
115 })
116
117 describe('Check playlists filters and privacies', function () {
118
119 it('Should list video playlist privacies', async function () {
120 const privacies = await commands[0].getPrivacies()
121
122 expect(Object.keys(privacies)).to.have.length.at.least(3)
123 expect(privacies[3]).to.equal('Private')
124 })
125
126 it('Should filter on playlist type', async function () {
127 this.timeout(30000)
128
129 const token = servers[0].accessToken
130
131 await commands[0].create({
132 attributes: {
133 displayName: 'my super playlist',
134 privacy: VideoPlaylistPrivacy.PUBLIC,
135 description: 'my super description',
136 thumbnailfile: 'custom-thumbnail.jpg',
137 videoChannelId: servers[0].store.channel.id
138 }
139 })
140
141 {
142 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
143
144 expect(body.total).to.equal(1)
145 expect(body.data).to.have.lengthOf(1)
146
147 const playlist = body.data[0]
148 expect(playlist.displayName).to.equal('Watch later')
149 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
150 expect(playlist.type.label).to.equal('Watch later')
151 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
152 }
153
154 {
155 const bodyList = await commands[0].list({ playlistType: VideoPlaylistType.WATCH_LATER })
156 const bodyChannel = await commands[0].listByChannel({ handle: 'root_channel', playlistType: VideoPlaylistType.WATCH_LATER })
157
158 for (const body of [ bodyList, bodyChannel ]) {
159 expect(body.total).to.equal(0)
160 expect(body.data).to.have.lengthOf(0)
161 }
162 }
163
164 {
165 const bodyList = await commands[0].list({ playlistType: VideoPlaylistType.REGULAR })
166 const bodyChannel = await commands[0].listByChannel({ handle: 'root_channel', playlistType: VideoPlaylistType.REGULAR })
167
168 let playlist: VideoPlaylist = null
169 for (const body of [ bodyList, bodyChannel ]) {
170
171 expect(body.total).to.equal(1)
172 expect(body.data).to.have.lengthOf(1)
173
174 playlist = body.data[0]
175 expect(playlist.displayName).to.equal('my super playlist')
176 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
177 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
178 }
179
180 await commands[0].update({
181 playlistId: playlist.id,
182 attributes: {
183 privacy: VideoPlaylistPrivacy.PRIVATE
184 }
185 })
186 }
187
188 {
189 const bodyList = await commands[0].list({ playlistType: VideoPlaylistType.REGULAR })
190 const bodyChannel = await commands[0].listByChannel({ handle: 'root_channel', playlistType: VideoPlaylistType.REGULAR })
191
192 for (const body of [ bodyList, bodyChannel ]) {
193 expect(body.total).to.equal(0)
194 expect(body.data).to.have.lengthOf(0)
195 }
196 }
197
198 {
199 const body = await commands[0].listByAccount({ handle: 'root' })
200 expect(body.total).to.equal(0)
201 expect(body.data).to.have.lengthOf(0)
202 }
203 })
204
205 it('Should get private playlist for a classic user', async function () {
206 const token = await servers[0].users.generateUserAndToken('toto')
207
208 const body = await commands[0].listByAccount({ token, handle: 'toto' })
209
210 expect(body.total).to.equal(1)
211 expect(body.data).to.have.lengthOf(1)
212
213 const playlistId = body.data[0].id
214 await commands[0].listVideos({ token, playlistId })
215 })
216 })
217
218 describe('Create and federate playlists', function () {
219
220 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
221 this.timeout(30000)
222
223 await commands[0].create({
224 attributes: {
225 displayName: 'my super playlist',
226 privacy: VideoPlaylistPrivacy.PUBLIC,
227 description: 'my super description',
228 thumbnailfile: 'custom-thumbnail.jpg',
229 videoChannelId: servers[0].store.channel.id
230 }
231 })
232
233 await waitJobs(servers)
234 // Processing a playlist by the receiver could be long
235 await wait(3000)
236
237 for (const server of servers) {
238 const body = await server.playlists.list({ start: 0, count: 5 })
239 expect(body.total).to.equal(1)
240 expect(body.data).to.have.lengthOf(1)
241
242 const playlistFromList = body.data[0]
243
244 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
245
246 for (const playlist of [ playlistFromGet, playlistFromList ]) {
247 expect(playlist.id).to.be.a('number')
248 expect(playlist.uuid).to.be.a('string')
249
250 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
251
252 expect(playlist.displayName).to.equal('my super playlist')
253 expect(playlist.description).to.equal('my super description')
254 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
255 expect(playlist.privacy.label).to.equal('Public')
256 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
257 expect(playlist.type.label).to.equal('Regular')
258 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
259
260 expect(playlist.videosLength).to.equal(0)
261
262 expect(playlist.ownerAccount.name).to.equal('root')
263 expect(playlist.ownerAccount.displayName).to.equal('root')
264 expect(playlist.videoChannel.name).to.equal('root_channel')
265 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
266 }
267 }
268 })
269
270 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
271 this.timeout(30000)
272
273 {
274 const playlist = await servers[1].playlists.create({
275 attributes: {
276 displayName: 'playlist 2',
277 privacy: VideoPlaylistPrivacy.PUBLIC,
278 videoChannelId: servers[1].store.channel.id
279 }
280 })
281 playlistServer2Id1 = playlist.id
282 }
283
284 {
285 const playlist = await servers[1].playlists.create({
286 attributes: {
287 displayName: 'playlist 3',
288 privacy: VideoPlaylistPrivacy.PUBLIC,
289 thumbnailfile: 'custom-thumbnail.jpg',
290 videoChannelId: servers[1].store.channel.id
291 }
292 })
293
294 playlistServer2Id2 = playlist.id
295 playlistServer2UUID2 = playlist.uuid
296 }
297
298 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
299 await servers[1].playlists.addElement({
300 playlistId: id,
301 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
302 })
303 await servers[1].playlists.addElement({
304 playlistId: id,
305 attributes: { videoId: servers[1].store.videos[1].id }
306 })
307 }
308
309 await waitJobs(servers)
310 await wait(3000)
311
312 for (const server of [ servers[0], servers[1] ]) {
313 const body = await server.playlists.list({ start: 0, count: 5 })
314
315 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
316 expect(playlist2).to.not.be.undefined
317 await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
318
319 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
320 expect(playlist3).to.not.be.undefined
321 await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', playlist3.thumbnailPath)
322 }
323
324 const body = await servers[2].playlists.list({ start: 0, count: 5 })
325 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
326 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
327 })
328
329 it('Should have the playlist on server 3 after a new follow', async function () {
330 this.timeout(30000)
331
332 // Server 2 and server 3 follow each other
333 await doubleFollow(servers[1], servers[2])
334
335 const body = await servers[2].playlists.list({ start: 0, count: 5 })
336
337 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
338 expect(playlist2).to.not.be.undefined
339 await testImageGeneratedByFFmpeg(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
340
341 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
342 })
343 })
344
345 describe('List playlists', function () {
346
347 it('Should correctly list the playlists', async function () {
348 this.timeout(30000)
349
350 {
351 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
352 expect(body.total).to.equal(3)
353
354 const data = body.data
355 expect(data).to.have.lengthOf(2)
356 expect(data[0].displayName).to.equal('playlist 2')
357 expect(data[1].displayName).to.equal('playlist 3')
358 }
359
360 {
361 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
362 expect(body.total).to.equal(3)
363
364 const data = body.data
365 expect(data).to.have.lengthOf(2)
366 expect(data[0].displayName).to.equal('playlist 2')
367 expect(data[1].displayName).to.equal('my super playlist')
368 }
369 })
370
371 it('Should list video channel playlists', async function () {
372 this.timeout(30000)
373
374 {
375 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
376 expect(body.total).to.equal(1)
377
378 const data = body.data
379 expect(data).to.have.lengthOf(1)
380 expect(data[0].displayName).to.equal('my super playlist')
381 }
382 })
383
384 it('Should list account playlists', async function () {
385 this.timeout(30000)
386
387 {
388 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
389 expect(body.total).to.equal(2)
390
391 const data = body.data
392 expect(data).to.have.lengthOf(1)
393 expect(data[0].displayName).to.equal('playlist 2')
394 }
395
396 {
397 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
398 expect(body.total).to.equal(2)
399
400 const data = body.data
401 expect(data).to.have.lengthOf(1)
402 expect(data[0].displayName).to.equal('playlist 3')
403 }
404
405 {
406 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
407 expect(body.total).to.equal(1)
408
409 const data = body.data
410 expect(data).to.have.lengthOf(1)
411 expect(data[0].displayName).to.equal('playlist 3')
412 }
413
414 {
415 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
416 expect(body.total).to.equal(0)
417
418 const data = body.data
419 expect(data).to.have.lengthOf(0)
420 }
421 })
422 })
423
424 describe('Playlist rights', function () {
425 let unlistedPlaylist: VideoPlaylistCreateResult
426 let privatePlaylist: VideoPlaylistCreateResult
427
428 before(async function () {
429 this.timeout(30000)
430
431 {
432 unlistedPlaylist = await servers[1].playlists.create({
433 attributes: {
434 displayName: 'playlist unlisted',
435 privacy: VideoPlaylistPrivacy.UNLISTED,
436 videoChannelId: servers[1].store.channel.id
437 }
438 })
439 }
440
441 {
442 privatePlaylist = await servers[1].playlists.create({
443 attributes: {
444 displayName: 'playlist private',
445 privacy: VideoPlaylistPrivacy.PRIVATE
446 }
447 })
448 }
449
450 await waitJobs(servers)
451 await wait(3000)
452 })
453
454 it('Should not list unlisted or private playlists', async function () {
455 for (const server of servers) {
456 const results = [
457 await server.playlists.listByAccount({ handle: 'root@' + servers[1].host, sort: '-createdAt' }),
458 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
459 ]
460
461 expect(results[0].total).to.equal(2)
462 expect(results[1].total).to.equal(3)
463
464 for (const body of results) {
465 const data = body.data
466 expect(data).to.have.lengthOf(2)
467 expect(data[0].displayName).to.equal('playlist 3')
468 expect(data[1].displayName).to.equal('playlist 2')
469 }
470 }
471 })
472
473 it('Should not get unlisted playlist using only the id', async function () {
474 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
475 })
476
477 it('Should get unlisted playlist using uuid or shortUUID', async function () {
478 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
479 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
480 })
481
482 it('Should not get private playlist without token', async function () {
483 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
484 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
485 }
486 })
487
488 it('Should get private playlist with a token', async function () {
489 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
490 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
491 }
492 })
493 })
494
495 describe('Update playlists', function () {
496
497 it('Should update a playlist', async function () {
498 this.timeout(30000)
499
500 await servers[1].playlists.update({
501 attributes: {
502 displayName: 'playlist 3 updated',
503 description: 'description updated',
504 privacy: VideoPlaylistPrivacy.UNLISTED,
505 thumbnailfile: 'custom-thumbnail.jpg',
506 videoChannelId: servers[1].store.channel.id
507 },
508 playlistId: playlistServer2Id2
509 })
510
511 await waitJobs(servers)
512
513 for (const server of servers) {
514 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
515
516 expect(playlist.displayName).to.equal('playlist 3 updated')
517 expect(playlist.description).to.equal('description updated')
518
519 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
520 expect(playlist.privacy.label).to.equal('Unlisted')
521
522 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
523 expect(playlist.type.label).to.equal('Regular')
524
525 expect(playlist.videosLength).to.equal(2)
526
527 expect(playlist.ownerAccount.name).to.equal('root')
528 expect(playlist.ownerAccount.displayName).to.equal('root')
529 expect(playlist.videoChannel.name).to.equal('root_channel')
530 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
531 }
532 })
533 })
534
535 describe('Element timestamps', function () {
536
537 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
538 this.timeout(30000)
539
540 const addVideo = (attributes: any) => {
541 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
542 }
543
544 const playlistDisplayName = 'playlist 4'
545 const playlist = await commands[0].create({
546 attributes: {
547 displayName: playlistDisplayName,
548 privacy: VideoPlaylistPrivacy.PUBLIC,
549 videoChannelId: servers[0].store.channel.id
550 }
551 })
552
553 playlistServer1Id = playlist.id
554 playlistServer1DisplayName = playlistDisplayName
555 playlistServer1UUID = playlist.uuid
556
557 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
558 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
559 await addVideo({ videoId: servers[2].store.videos[2].uuid })
560 {
561 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
562 playlistElementServer1Video4 = element.id
563 }
564
565 {
566 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
567 playlistElementServer1Video5 = element.id
568 }
569
570 {
571 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
572 playlistElementNSFW = element.id
573
574 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
575 await addVideo({ videoId: nsfwVideoServer1 })
576 }
577
578 await waitJobs(servers)
579 })
580
581 it('Should correctly list playlist videos', async function () {
582 this.timeout(30000)
583
584 for (const server of servers) {
585 {
586 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
587
588 expect(body.total).to.equal(8)
589
590 const videoElements = body.data
591 expect(videoElements).to.have.lengthOf(8)
592
593 expect(videoElements[0].video.name).to.equal('video 0 server 1')
594 expect(videoElements[0].position).to.equal(1)
595 expect(videoElements[0].startTimestamp).to.equal(15)
596 expect(videoElements[0].stopTimestamp).to.equal(28)
597
598 expect(videoElements[1].video.name).to.equal('video 1 server 3')
599 expect(videoElements[1].position).to.equal(2)
600 expect(videoElements[1].startTimestamp).to.equal(35)
601 expect(videoElements[1].stopTimestamp).to.be.null
602
603 expect(videoElements[2].video.name).to.equal('video 2 server 3')
604 expect(videoElements[2].position).to.equal(3)
605 expect(videoElements[2].startTimestamp).to.be.null
606 expect(videoElements[2].stopTimestamp).to.be.null
607
608 expect(videoElements[3].video.name).to.equal('video 3 server 1')
609 expect(videoElements[3].position).to.equal(4)
610 expect(videoElements[3].startTimestamp).to.be.null
611 expect(videoElements[3].stopTimestamp).to.equal(35)
612
613 expect(videoElements[4].video.name).to.equal('video 4 server 1')
614 expect(videoElements[4].position).to.equal(5)
615 expect(videoElements[4].startTimestamp).to.equal(45)
616 expect(videoElements[4].stopTimestamp).to.equal(60)
617
618 expect(videoElements[5].video.name).to.equal('NSFW video')
619 expect(videoElements[5].position).to.equal(6)
620 expect(videoElements[5].startTimestamp).to.equal(5)
621 expect(videoElements[5].stopTimestamp).to.be.null
622
623 expect(videoElements[6].video.name).to.equal('NSFW video')
624 expect(videoElements[6].position).to.equal(7)
625 expect(videoElements[6].startTimestamp).to.equal(4)
626 expect(videoElements[6].stopTimestamp).to.be.null
627
628 expect(videoElements[7].video.name).to.equal('NSFW video')
629 expect(videoElements[7].position).to.equal(8)
630 expect(videoElements[7].startTimestamp).to.be.null
631 expect(videoElements[7].stopTimestamp).to.be.null
632 }
633
634 {
635 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
636 expect(body.data).to.have.lengthOf(2)
637 }
638 }
639 })
640 })
641
642 describe('Element type', function () {
643 let groupUser1: PeerTubeServer[]
644 let groupWithoutToken1: PeerTubeServer[]
645 let group1: PeerTubeServer[]
646 let group2: PeerTubeServer[]
647
648 let video1: string
649 let video2: string
650 let video3: string
651
652 before(async function () {
653 this.timeout(60000)
654
655 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
656 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
657 group1 = [ servers[0] ]
658 group2 = [ servers[1], servers[2] ]
659
660 const playlist = await commands[0].create({
661 token: userTokenServer1,
662 attributes: {
663 displayName: 'playlist 56',
664 privacy: VideoPlaylistPrivacy.PUBLIC,
665 videoChannelId: servers[0].store.channel.id
666 }
667 })
668
669 const playlistServer1Id2 = playlist.id
670 playlistServer1UUID2 = playlist.uuid
671
672 const addVideo = (attributes: any) => {
673 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
674 }
675
676 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
677 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
678 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
679
680 await waitJobs(servers)
681
682 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
683 await addVideo({ videoId: video2, startTimestamp: 35 })
684 await addVideo({ videoId: video3 })
685
686 await waitJobs(servers)
687 })
688
689 it('Should update the element type if the video is private/password protected', async function () {
690 this.timeout(20000)
691
692 const name = 'video 89'
693 const position = 1
694
695 {
696 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
697 await waitJobs(servers)
698
699 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
700 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
701 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
702 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
703 }
704
705 {
706 await servers[0].videos.update({
707 id: video1,
708 attributes: { privacy: VideoPrivacy.PASSWORD_PROTECTED, videoPasswords: [ 'password' ] }
709 })
710 await waitJobs(servers)
711
712 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
713 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
714 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
715 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
716 }
717
718 {
719 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
720 await waitJobs(servers)
721
722 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
723 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
724 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
725 // We deleted the video, so even if we recreated it, the old entry is still deleted
726 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
727 }
728 })
729
730 it('Should update the element type if the video is blacklisted', async function () {
731 this.timeout(20000)
732
733 const name = 'video 89'
734 const position = 1
735
736 {
737 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
738 await waitJobs(servers)
739
740 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
741 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
742 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
743 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
744 }
745
746 {
747 await servers[0].blacklist.remove({ videoId: video1 })
748 await waitJobs(servers)
749
750 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
751 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
752 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
753 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
754 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
755 }
756 })
757
758 it('Should update the element type if the account or server of the video is blocked', async function () {
759 this.timeout(90000)
760
761 const command = servers[0].blocklist
762
763 const name = 'video 90'
764 const position = 2
765
766 {
767 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@' + servers[1].host })
768 await waitJobs(servers)
769
770 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
771 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
772
773 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@' + servers[1].host })
774 await waitJobs(servers)
775
776 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
777 }
778
779 {
780 await command.addToMyBlocklist({ token: userTokenServer1, server: servers[1].host })
781 await waitJobs(servers)
782
783 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
784 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
785
786 await command.removeFromMyBlocklist({ token: userTokenServer1, server: servers[1].host })
787 await waitJobs(servers)
788
789 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
790 }
791
792 {
793 await command.addToServerBlocklist({ account: 'root@' + servers[1].host })
794 await waitJobs(servers)
795
796 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
797 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
798
799 await command.removeFromServerBlocklist({ account: 'root@' + servers[1].host })
800 await waitJobs(servers)
801
802 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
803 }
804
805 {
806 await command.addToServerBlocklist({ server: servers[1].host })
807 await waitJobs(servers)
808
809 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
810 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
811
812 await command.removeFromServerBlocklist({ server: servers[1].host })
813 await waitJobs(servers)
814
815 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
816 }
817 })
818 })
819
820 describe('Managing playlist elements', function () {
821
822 it('Should reorder the playlist', async function () {
823 this.timeout(30000)
824
825 {
826 await commands[0].reorderElements({
827 playlistId: playlistServer1Id,
828 attributes: {
829 startPosition: 2,
830 insertAfterPosition: 3
831 }
832 })
833
834 await waitJobs(servers)
835
836 for (const server of servers) {
837 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
838 const names = body.data.map(v => v.video.name)
839
840 expect(names).to.deep.equal([
841 'video 0 server 1',
842 'video 2 server 3',
843 'video 1 server 3',
844 'video 3 server 1',
845 'video 4 server 1',
846 'NSFW video',
847 'NSFW video',
848 'NSFW video'
849 ])
850 }
851 }
852
853 {
854 await commands[0].reorderElements({
855 playlistId: playlistServer1Id,
856 attributes: {
857 startPosition: 1,
858 reorderLength: 3,
859 insertAfterPosition: 4
860 }
861 })
862
863 await waitJobs(servers)
864
865 for (const server of servers) {
866 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
867 const names = body.data.map(v => v.video.name)
868
869 expect(names).to.deep.equal([
870 'video 3 server 1',
871 'video 0 server 1',
872 'video 2 server 3',
873 'video 1 server 3',
874 'video 4 server 1',
875 'NSFW video',
876 'NSFW video',
877 'NSFW video'
878 ])
879 }
880 }
881
882 {
883 await commands[0].reorderElements({
884 playlistId: playlistServer1Id,
885 attributes: {
886 startPosition: 6,
887 insertAfterPosition: 3
888 }
889 })
890
891 await waitJobs(servers)
892
893 for (const server of servers) {
894 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
895 const names = elements.map(v => v.video.name)
896
897 expect(names).to.deep.equal([
898 'video 3 server 1',
899 'video 0 server 1',
900 'video 2 server 3',
901 'NSFW video',
902 'video 1 server 3',
903 'video 4 server 1',
904 'NSFW video',
905 'NSFW video'
906 ])
907
908 for (let i = 1; i <= elements.length; i++) {
909 expect(elements[i - 1].position).to.equal(i)
910 }
911 }
912 }
913 })
914
915 it('Should update startTimestamp/endTimestamp of some elements', async function () {
916 this.timeout(30000)
917
918 await commands[0].updateElement({
919 playlistId: playlistServer1Id,
920 elementId: playlistElementServer1Video4,
921 attributes: {
922 startTimestamp: 1
923 }
924 })
925
926 await commands[0].updateElement({
927 playlistId: playlistServer1Id,
928 elementId: playlistElementServer1Video5,
929 attributes: {
930 stopTimestamp: null
931 }
932 })
933
934 await waitJobs(servers)
935
936 for (const server of servers) {
937 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
938
939 expect(elements[0].video.name).to.equal('video 3 server 1')
940 expect(elements[0].position).to.equal(1)
941 expect(elements[0].startTimestamp).to.equal(1)
942 expect(elements[0].stopTimestamp).to.equal(35)
943
944 expect(elements[5].video.name).to.equal('video 4 server 1')
945 expect(elements[5].position).to.equal(6)
946 expect(elements[5].startTimestamp).to.equal(45)
947 expect(elements[5].stopTimestamp).to.be.null
948 }
949 })
950
951 it('Should check videos existence in my playlist', async function () {
952 const videoIds = [
953 servers[0].store.videos[0].id,
954 42000,
955 servers[0].store.videos[3].id,
956 43000,
957 servers[0].store.videos[4].id
958 ]
959 const obj = await commands[0].videosExist({ videoIds })
960
961 {
962 const elem = obj[servers[0].store.videos[0].id]
963 expect(elem).to.have.lengthOf(1)
964 expect(elem[0].playlistElementId).to.exist
965 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
966 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
967 expect(elem[0].playlistId).to.equal(playlistServer1Id)
968 expect(elem[0].startTimestamp).to.equal(15)
969 expect(elem[0].stopTimestamp).to.equal(28)
970 }
971
972 {
973 const elem = obj[servers[0].store.videos[3].id]
974 expect(elem).to.have.lengthOf(1)
975 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
976 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
977 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
978 expect(elem[0].playlistId).to.equal(playlistServer1Id)
979 expect(elem[0].startTimestamp).to.equal(1)
980 expect(elem[0].stopTimestamp).to.equal(35)
981 }
982
983 {
984 const elem = obj[servers[0].store.videos[4].id]
985 expect(elem).to.have.lengthOf(1)
986 expect(elem[0].playlistId).to.equal(playlistServer1Id)
987 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
988 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
989 expect(elem[0].startTimestamp).to.equal(45)
990 expect(elem[0].stopTimestamp).to.equal(null)
991 }
992
993 expect(obj[42000]).to.have.lengthOf(0)
994 expect(obj[43000]).to.have.lengthOf(0)
995 })
996
997 it('Should automatically update updatedAt field of playlists', async function () {
998 const server = servers[1]
999 const videoId = servers[1].store.videos[5].id
1000
1001 async function getPlaylistNames () {
1002 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
1003
1004 return data.map(p => p.displayName)
1005 }
1006
1007 const attributes = { videoId }
1008 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
1009 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
1010
1011 const names1 = await getPlaylistNames()
1012 expect(names1[0]).to.equal('playlist 3 updated')
1013 expect(names1[1]).to.equal('playlist 2')
1014
1015 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
1016
1017 const names2 = await getPlaylistNames()
1018 expect(names2[0]).to.equal('playlist 2')
1019 expect(names2[1]).to.equal('playlist 3 updated')
1020
1021 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
1022
1023 const names3 = await getPlaylistNames()
1024 expect(names3[0]).to.equal('playlist 3 updated')
1025 expect(names3[1]).to.equal('playlist 2')
1026 })
1027
1028 it('Should delete some elements', async function () {
1029 this.timeout(30000)
1030
1031 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
1032 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
1033
1034 await waitJobs(servers)
1035
1036 for (const server of servers) {
1037 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
1038 expect(body.total).to.equal(6)
1039
1040 const elements = body.data
1041 expect(elements).to.have.lengthOf(6)
1042
1043 expect(elements[0].video.name).to.equal('video 0 server 1')
1044 expect(elements[0].position).to.equal(1)
1045
1046 expect(elements[1].video.name).to.equal('video 2 server 3')
1047 expect(elements[1].position).to.equal(2)
1048
1049 expect(elements[2].video.name).to.equal('video 1 server 3')
1050 expect(elements[2].position).to.equal(3)
1051
1052 expect(elements[3].video.name).to.equal('video 4 server 1')
1053 expect(elements[3].position).to.equal(4)
1054
1055 expect(elements[4].video.name).to.equal('NSFW video')
1056 expect(elements[4].position).to.equal(5)
1057
1058 expect(elements[5].video.name).to.equal('NSFW video')
1059 expect(elements[5].position).to.equal(6)
1060 }
1061 })
1062
1063 it('Should be able to create a public playlist, and set it to private', async function () {
1064 this.timeout(30000)
1065
1066 const videoPlaylistIds = await commands[0].create({
1067 attributes: {
1068 displayName: 'my super public playlist',
1069 privacy: VideoPlaylistPrivacy.PUBLIC,
1070 videoChannelId: servers[0].store.channel.id
1071 }
1072 })
1073
1074 await waitJobs(servers)
1075
1076 for (const server of servers) {
1077 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1078 }
1079
1080 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1081 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1082
1083 await waitJobs(servers)
1084
1085 for (const server of [ servers[1], servers[2] ]) {
1086 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1087 }
1088
1089 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1090 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1091 })
1092 })
1093
1094 describe('Playlist deletion', function () {
1095
1096 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1097 this.timeout(30000)
1098
1099 await commands[0].delete({ playlistId: playlistServer1Id })
1100
1101 await waitJobs(servers)
1102
1103 for (const server of servers) {
1104 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1105 }
1106 })
1107
1108 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1109 this.timeout(30000)
1110
1111 for (const server of servers) {
1112 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server)
1113 }
1114 })
1115
1116 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1117 this.timeout(30000)
1118
1119 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1120
1121 {
1122 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1123 expect(body.total).to.equal(3)
1124
1125 expect(finder(body.data)).to.not.be.undefined
1126 }
1127
1128 await servers[2].follows.unfollow({ target: servers[0] })
1129
1130 {
1131 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1132 expect(body.total).to.equal(1)
1133
1134 expect(finder(body.data)).to.be.undefined
1135 }
1136 })
1137
1138 it('Should delete a channel and put the associated playlist in private mode', async function () {
1139 this.timeout(30000)
1140
1141 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1142
1143 const playlistCreated = await commands[0].create({
1144 attributes: {
1145 displayName: 'channel playlist',
1146 privacy: VideoPlaylistPrivacy.PUBLIC,
1147 videoChannelId: channel.id
1148 }
1149 })
1150
1151 await waitJobs(servers)
1152
1153 await servers[0].channels.delete({ channelName: 'super_channel' })
1154
1155 await waitJobs(servers)
1156
1157 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1158 expect(body.displayName).to.equal('channel playlist')
1159 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1160
1161 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1162 })
1163
1164 it('Should delete an account and delete its playlists', async function () {
1165 this.timeout(30000)
1166
1167 const { userId, token } = await servers[0].users.generate('user_1')
1168
1169 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1170 const userChannel = videoChannels[0]
1171
1172 await commands[0].create({
1173 attributes: {
1174 displayName: 'playlist to be deleted',
1175 privacy: VideoPlaylistPrivacy.PUBLIC,
1176 videoChannelId: userChannel.id
1177 }
1178 })
1179
1180 await waitJobs(servers)
1181
1182 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1183
1184 {
1185 for (const server of [ servers[0], servers[1] ]) {
1186 const body = await server.playlists.list({ start: 0, count: 15 })
1187
1188 expect(finder(body.data)).to.not.be.undefined
1189 }
1190 }
1191
1192 await servers[0].users.remove({ userId })
1193 await waitJobs(servers)
1194
1195 {
1196 for (const server of [ servers[0], servers[1] ]) {
1197 const body = await server.playlists.list({ start: 0, count: 15 })
1198
1199 expect(finder(body.data)).to.be.undefined
1200 }
1201 }
1202 })
1203 })
1204
1205 after(async function () {
1206 await cleanupTests(servers)
1207 })
1208})
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
deleted file mode 100644
index de96bcfcc..000000000
--- a/server/tests/api/videos/video-privacy.ts
+++ /dev/null
@@ -1,287 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
6import { cleanupTests, createSingleServer, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
7
8describe('Test video privacy', function () {
9 const servers: PeerTubeServer[] = []
10 let anotherUserToken: string
11
12 let privateVideoId: number
13 let privateVideoUUID: string
14
15 let internalVideoId: number
16 let internalVideoUUID: string
17
18 let unlistedVideo: VideoCreateResult
19 let nonFederatedUnlistedVideoUUID: string
20
21 let now: number
22
23 const dontFederateUnlistedConfig = {
24 federation: {
25 videos: {
26 federate_unlisted: false
27 }
28 }
29 }
30
31 before(async function () {
32 this.timeout(50000)
33
34 // Run servers
35 servers.push(await createSingleServer(1, dontFederateUnlistedConfig))
36 servers.push(await createSingleServer(2))
37
38 // Get the access tokens
39 await setAccessTokensToServers(servers)
40
41 // Server 1 and server 2 follow each other
42 await doubleFollow(servers[0], servers[1])
43 })
44
45 describe('Private and internal videos', function () {
46
47 it('Should upload a private and internal videos on server 1', async function () {
48 this.timeout(50000)
49
50 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
51 const attributes = { privacy }
52 await servers[0].videos.upload({ attributes })
53 }
54
55 await waitJobs(servers)
56 })
57
58 it('Should not have these private and internal videos on server 2', async function () {
59 const { total, data } = await servers[1].videos.list()
60
61 expect(total).to.equal(0)
62 expect(data).to.have.lengthOf(0)
63 })
64
65 it('Should not list the private and internal videos for an unauthenticated user on server 1', async function () {
66 const { total, data } = await servers[0].videos.list()
67
68 expect(total).to.equal(0)
69 expect(data).to.have.lengthOf(0)
70 })
71
72 it('Should not list the private video and list the internal video for an authenticated user on server 1', async function () {
73 const { total, data } = await servers[0].videos.listWithToken()
74
75 expect(total).to.equal(1)
76 expect(data).to.have.lengthOf(1)
77
78 expect(data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL)
79 })
80
81 it('Should list my (private and internal) videos', async function () {
82 const { total, data } = await servers[0].videos.listMyVideos()
83
84 expect(total).to.equal(2)
85 expect(data).to.have.lengthOf(2)
86
87 const privateVideo = data.find(v => v.privacy.id === VideoPrivacy.PRIVATE)
88 privateVideoId = privateVideo.id
89 privateVideoUUID = privateVideo.uuid
90
91 const internalVideo = data.find(v => v.privacy.id === VideoPrivacy.INTERNAL)
92 internalVideoId = internalVideo.id
93 internalVideoUUID = internalVideo.uuid
94 })
95
96 it('Should not be able to watch the private/internal video with non authenticated user', async function () {
97 await servers[0].videos.get({ id: privateVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
98 await servers[0].videos.get({ id: internalVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
99 })
100
101 it('Should not be able to watch the private video with another user', async function () {
102 const user = {
103 username: 'hello',
104 password: 'super password'
105 }
106 await servers[0].users.create({ username: user.username, password: user.password })
107
108 anotherUserToken = await servers[0].login.getAccessToken(user)
109
110 await servers[0].videos.getWithToken({
111 token: anotherUserToken,
112 id: privateVideoUUID,
113 expectedStatus: HttpStatusCode.FORBIDDEN_403
114 })
115 })
116
117 it('Should be able to watch the internal video with another user', async function () {
118 await servers[0].videos.getWithToken({ token: anotherUserToken, id: internalVideoUUID })
119 })
120
121 it('Should be able to watch the private video with the correct user', async function () {
122 await servers[0].videos.getWithToken({ id: privateVideoUUID })
123 })
124 })
125
126 describe('Unlisted videos', function () {
127
128 it('Should upload an unlisted video on server 2', async function () {
129 this.timeout(120000)
130
131 const attributes = {
132 name: 'unlisted video',
133 privacy: VideoPrivacy.UNLISTED
134 }
135 await servers[1].videos.upload({ attributes })
136
137 // Server 2 has transcoding enabled
138 await waitJobs(servers)
139 })
140
141 it('Should not have this unlisted video listed on server 1 and 2', async function () {
142 for (const server of servers) {
143 const { total, data } = await server.videos.list()
144
145 expect(total).to.equal(0)
146 expect(data).to.have.lengthOf(0)
147 }
148 })
149
150 it('Should list my (unlisted) videos', async function () {
151 const { total, data } = await servers[1].videos.listMyVideos()
152
153 expect(total).to.equal(1)
154 expect(data).to.have.lengthOf(1)
155
156 unlistedVideo = data[0]
157 })
158
159 it('Should not be able to get this unlisted video using its id', async function () {
160 await servers[1].videos.get({ id: unlistedVideo.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
161 })
162
163 it('Should be able to get this unlisted video using its uuid/shortUUID', async function () {
164 for (const server of servers) {
165 for (const id of [ unlistedVideo.uuid, unlistedVideo.shortUUID ]) {
166 const video = await server.videos.get({ id })
167
168 expect(video.name).to.equal('unlisted video')
169 }
170 }
171 })
172
173 it('Should upload a non-federating unlisted video to server 1', async function () {
174 this.timeout(30000)
175
176 const attributes = {
177 name: 'unlisted video',
178 privacy: VideoPrivacy.UNLISTED
179 }
180 await servers[0].videos.upload({ attributes })
181
182 await waitJobs(servers)
183 })
184
185 it('Should list my new unlisted video', async function () {
186 const { total, data } = await servers[0].videos.listMyVideos()
187
188 expect(total).to.equal(3)
189 expect(data).to.have.lengthOf(3)
190
191 nonFederatedUnlistedVideoUUID = data[0].uuid
192 })
193
194 it('Should be able to get non-federated unlisted video from origin', async function () {
195 const video = await servers[0].videos.get({ id: nonFederatedUnlistedVideoUUID })
196
197 expect(video.name).to.equal('unlisted video')
198 })
199
200 it('Should not be able to get non-federated unlisted video from federated server', async function () {
201 await servers[1].videos.get({ id: nonFederatedUnlistedVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
202 })
203 })
204
205 describe('Privacy update', function () {
206
207 it('Should update the private and internal videos to public on server 1', async function () {
208 this.timeout(100000)
209
210 now = Date.now()
211
212 {
213 const attributes = {
214 name: 'private video becomes public',
215 privacy: VideoPrivacy.PUBLIC
216 }
217
218 await servers[0].videos.update({ id: privateVideoId, attributes })
219 }
220
221 {
222 const attributes = {
223 name: 'internal video becomes public',
224 privacy: VideoPrivacy.PUBLIC
225 }
226 await servers[0].videos.update({ id: internalVideoId, attributes })
227 }
228
229 await wait(10000)
230 await waitJobs(servers)
231 })
232
233 it('Should have this new public video listed on server 1 and 2', async function () {
234 for (const server of servers) {
235 const { total, data } = await server.videos.list()
236 expect(total).to.equal(2)
237 expect(data).to.have.lengthOf(2)
238
239 const privateVideo = data.find(v => v.name === 'private video becomes public')
240 const internalVideo = data.find(v => v.name === 'internal video becomes public')
241
242 expect(privateVideo).to.not.be.undefined
243 expect(internalVideo).to.not.be.undefined
244
245 expect(new Date(privateVideo.publishedAt).getTime()).to.be.at.least(now)
246 // We don't change the publish date of internal videos
247 expect(new Date(internalVideo.publishedAt).getTime()).to.be.below(now)
248
249 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC)
250 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC)
251 }
252 })
253
254 it('Should set these videos as private and internal', async function () {
255 await servers[0].videos.update({ id: internalVideoId, attributes: { privacy: VideoPrivacy.PRIVATE } })
256 await servers[0].videos.update({ id: privateVideoId, attributes: { privacy: VideoPrivacy.INTERNAL } })
257
258 await waitJobs(servers)
259
260 for (const server of servers) {
261 const { total, data } = await server.videos.list()
262
263 expect(total).to.equal(0)
264 expect(data).to.have.lengthOf(0)
265 }
266
267 {
268 const { total, data } = await servers[0].videos.listMyVideos()
269 expect(total).to.equal(3)
270 expect(data).to.have.lengthOf(3)
271
272 const privateVideo = data.find(v => v.name === 'private video becomes public')
273 const internalVideo = data.find(v => v.name === 'internal video becomes public')
274
275 expect(privateVideo).to.not.be.undefined
276 expect(internalVideo).to.not.be.undefined
277
278 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.INTERNAL)
279 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PRIVATE)
280 }
281 })
282 })
283
284 after(async function () {
285 await cleanupTests(servers)
286 })
287})
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts
deleted file mode 100644
index bf341c648..000000000
--- a/server/tests/api/videos/video-schedule-update.ts
+++ /dev/null
@@ -1,155 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
14
15function in10Seconds () {
16 const now = new Date()
17 now.setSeconds(now.getSeconds() + 10)
18
19 return now
20}
21
22describe('Test video update scheduler', function () {
23 let servers: PeerTubeServer[] = []
24 let video2UUID: string
25
26 before(async function () {
27 this.timeout(30000)
28
29 // Run servers
30 servers = await createMultipleServers(2)
31
32 await setAccessTokensToServers(servers)
33
34 await doubleFollow(servers[0], servers[1])
35 })
36
37 it('Should upload a video and schedule an update in 10 seconds', async function () {
38 const attributes = {
39 name: 'video 1',
40 privacy: VideoPrivacy.PRIVATE,
41 scheduleUpdate: {
42 updateAt: in10Seconds().toISOString(),
43 privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC
44 }
45 }
46
47 await servers[0].videos.upload({ attributes })
48
49 await waitJobs(servers)
50 })
51
52 it('Should not list the video (in privacy mode)', async function () {
53 for (const server of servers) {
54 const { total } = await server.videos.list()
55
56 expect(total).to.equal(0)
57 }
58 })
59
60 it('Should have my scheduled video in my account videos', async function () {
61 const { total, data } = await servers[0].videos.listMyVideos()
62 expect(total).to.equal(1)
63
64 const videoFromList = data[0]
65 const videoFromGet = await servers[0].videos.getWithToken({ id: videoFromList.uuid })
66
67 for (const video of [ videoFromList, videoFromGet ]) {
68 expect(video.name).to.equal('video 1')
69 expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE)
70 expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date())
71 expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC)
72 }
73 })
74
75 it('Should wait some seconds and have the video in public privacy', async function () {
76 this.timeout(50000)
77
78 await wait(15000)
79 await waitJobs(servers)
80
81 for (const server of servers) {
82 const { total, data } = await server.videos.list()
83
84 expect(total).to.equal(1)
85 expect(data[0].name).to.equal('video 1')
86 }
87 })
88
89 it('Should upload a video without scheduling an update', async function () {
90 const attributes = {
91 name: 'video 2',
92 privacy: VideoPrivacy.PRIVATE
93 }
94
95 const { uuid } = await servers[0].videos.upload({ attributes })
96 video2UUID = uuid
97
98 await waitJobs(servers)
99 })
100
101 it('Should update a video by scheduling an update', async function () {
102 const attributes = {
103 name: 'video 2 updated',
104 scheduleUpdate: {
105 updateAt: in10Seconds().toISOString(),
106 privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC
107 }
108 }
109
110 await servers[0].videos.update({ id: video2UUID, attributes })
111 await waitJobs(servers)
112 })
113
114 it('Should not display the updated video', async function () {
115 for (const server of servers) {
116 const { total } = await server.videos.list()
117
118 expect(total).to.equal(1)
119 }
120 })
121
122 it('Should have my scheduled updated video in my account videos', async function () {
123 const { total, data } = await servers[0].videos.listMyVideos()
124 expect(total).to.equal(2)
125
126 const video = data.find(v => v.uuid === video2UUID)
127 expect(video).not.to.be.undefined
128
129 expect(video.name).to.equal('video 2 updated')
130 expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE)
131
132 expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date())
133 expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC)
134 })
135
136 it('Should wait some seconds and have the updated video in public privacy', async function () {
137 this.timeout(20000)
138
139 await wait(15000)
140 await waitJobs(servers)
141
142 for (const server of servers) {
143 const { total, data } = await server.videos.list()
144 expect(total).to.equal(2)
145
146 const video = data.find(v => v.uuid === video2UUID)
147 expect(video).not.to.be.undefined
148 expect(video.name).to.equal('video 2 updated')
149 }
150 })
151
152 after(async function () {
153 await cleanupTests(servers)
154 })
155})
diff --git a/server/tests/api/videos/video-source.ts b/server/tests/api/videos/video-source.ts
deleted file mode 100644
index 1f394f904..000000000
--- a/server/tests/api/videos/video-source.ts
+++ /dev/null
@@ -1,447 +0,0 @@
1import { expect } from 'chai'
2import { expectStartWith } from '@server/tests/shared'
3/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4import { areMockObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils'
5import { HttpStatusCode } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 makeGetRequest,
11 makeRawRequest,
12 ObjectStorageCommand,
13 PeerTubeServer,
14 setAccessTokensToServers,
15 setDefaultAccountAvatar,
16 setDefaultVideoChannel,
17 waitJobs
18} from '@shared/server-commands'
19
20describe('Test a video file replacement', function () {
21 let servers: PeerTubeServer[] = []
22
23 let replaceDate: Date
24 let userToken: string
25 let uuid: string
26
27 before(async function () {
28 this.timeout(50000)
29
30 servers = await createMultipleServers(2)
31
32 // Get the access tokens
33 await setAccessTokensToServers(servers)
34 await setDefaultVideoChannel(servers)
35 await setDefaultAccountAvatar(servers)
36
37 await servers[0].config.enableFileUpdate()
38
39 userToken = await servers[0].users.generateUserAndToken('user1')
40
41 // Server 1 and server 2 follow each other
42 await doubleFollow(servers[0], servers[1])
43 })
44
45 describe('Getting latest video source', () => {
46 const fixture = 'video_short.webm'
47 const uuids: string[] = []
48
49 it('Should get the source filename with legacy upload', async function () {
50 this.timeout(30000)
51
52 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'my video', fixture }, mode: 'legacy' })
53 uuids.push(uuid)
54
55 const source = await servers[0].videos.getSource({ id: uuid })
56 expect(source.filename).to.equal(fixture)
57 })
58
59 it('Should get the source filename with resumable upload', async function () {
60 this.timeout(30000)
61
62 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'my video', fixture }, mode: 'resumable' })
63 uuids.push(uuid)
64
65 const source = await servers[0].videos.getSource({ id: uuid })
66 expect(source.filename).to.equal(fixture)
67 })
68
69 after(async function () {
70 this.timeout(60000)
71
72 for (const uuid of uuids) {
73 await servers[0].videos.remove({ id: uuid })
74 }
75
76 await waitJobs(servers)
77 })
78 })
79
80 describe('Updating video source', function () {
81
82 describe('Filesystem', function () {
83
84 it('Should replace a video file with transcoding disabled', async function () {
85 this.timeout(120000)
86
87 await servers[0].config.disableTranscoding()
88
89 const { uuid } = await servers[0].videos.quickUpload({ name: 'fs without transcoding', fixture: 'video_short_720p.mp4' })
90 await waitJobs(servers)
91
92 for (const server of servers) {
93 const video = await server.videos.get({ id: uuid })
94
95 const files = getAllFiles(video)
96 expect(files).to.have.lengthOf(1)
97 expect(files[0].resolution.id).to.equal(720)
98 }
99
100 await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture: 'video_short_360p.mp4' })
101 await waitJobs(servers)
102
103 for (const server of servers) {
104 const video = await server.videos.get({ id: uuid })
105
106 const files = getAllFiles(video)
107 expect(files).to.have.lengthOf(1)
108 expect(files[0].resolution.id).to.equal(360)
109 }
110 })
111
112 it('Should replace a video file with transcoding enabled', async function () {
113 this.timeout(120000)
114
115 const previousPaths: string[] = []
116
117 await servers[0].config.enableTranscoding({ hls: true, webVideo: true, with0p: true })
118
119 const { uuid: videoUUID } = await servers[0].videos.quickUpload({ name: 'fs with transcoding', fixture: 'video_short_720p.mp4' })
120 uuid = videoUUID
121
122 await waitJobs(servers)
123
124 for (const server of servers) {
125 const video = await server.videos.get({ id: uuid })
126 expect(video.inputFileUpdatedAt).to.be.null
127
128 const files = getAllFiles(video)
129 expect(files).to.have.lengthOf(6 * 2)
130
131 // Grab old paths to ensure we'll regenerate
132
133 previousPaths.push(video.previewPath)
134 previousPaths.push(video.thumbnailPath)
135
136 for (const file of files) {
137 previousPaths.push(file.fileUrl)
138 previousPaths.push(file.torrentUrl)
139 previousPaths.push(file.metadataUrl)
140
141 const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
142 previousPaths.push(JSON.stringify(metadata))
143 }
144
145 const { storyboards } = await server.storyboard.list({ id: uuid })
146 for (const s of storyboards) {
147 previousPaths.push(s.storyboardPath)
148 }
149 }
150
151 replaceDate = new Date()
152
153 await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture: 'video_short_360p.mp4' })
154 await waitJobs(servers)
155
156 for (const server of servers) {
157 const video = await server.videos.get({ id: uuid })
158
159 expect(video.inputFileUpdatedAt).to.not.be.null
160 expect(new Date(video.inputFileUpdatedAt)).to.be.above(replaceDate)
161
162 const files = getAllFiles(video)
163 expect(files).to.have.lengthOf(4 * 2)
164
165 expect(previousPaths).to.not.include(video.previewPath)
166 expect(previousPaths).to.not.include(video.thumbnailPath)
167
168 await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
169 await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
170
171 for (const file of files) {
172 expect(previousPaths).to.not.include(file.fileUrl)
173 expect(previousPaths).to.not.include(file.torrentUrl)
174 expect(previousPaths).to.not.include(file.metadataUrl)
175
176 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
177 await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
178
179 const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
180 expect(previousPaths).to.not.include(JSON.stringify(metadata))
181 }
182
183 const { storyboards } = await server.storyboard.list({ id: uuid })
184 for (const s of storyboards) {
185 expect(previousPaths).to.not.include(s.storyboardPath)
186
187 await makeGetRequest({ url: server.url, path: s.storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
188 }
189 }
190
191 await servers[0].config.enableMinimumTranscoding()
192 })
193
194 it('Should have cleaned up old files', async function () {
195 {
196 const count = await servers[0].servers.countFiles('storyboards')
197 expect(count).to.equal(2)
198 }
199
200 {
201 const count = await servers[0].servers.countFiles('web-videos')
202 expect(count).to.equal(5 + 1) // +1 for private directory
203 }
204
205 {
206 const count = await servers[0].servers.countFiles('streaming-playlists/hls')
207 expect(count).to.equal(1 + 1) // +1 for private directory
208 }
209
210 {
211 const count = await servers[0].servers.countFiles('torrents')
212 expect(count).to.equal(9)
213 }
214 })
215
216 it('Should have the correct source input', async function () {
217 const source = await servers[0].videos.getSource({ id: uuid })
218
219 expect(source.filename).to.equal('video_short_360p.mp4')
220 expect(new Date(source.createdAt)).to.be.above(replaceDate)
221 })
222
223 it('Should not have regenerated miniatures that were previously uploaded', async function () {
224 this.timeout(120000)
225
226 const { uuid } = await servers[0].videos.upload({
227 attributes: {
228 name: 'custom miniatures',
229 thumbnailfile: 'custom-thumbnail.jpg',
230 previewfile: 'custom-preview.jpg'
231 }
232 })
233
234 await waitJobs(servers)
235
236 const previousPaths: string[] = []
237
238 for (const server of servers) {
239 const video = await server.videos.get({ id: uuid })
240
241 previousPaths.push(video.previewPath)
242 previousPaths.push(video.thumbnailPath)
243
244 await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
245 await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
246 }
247
248 await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture: 'video_short_360p.mp4' })
249 await waitJobs(servers)
250
251 for (const server of servers) {
252 const video = await server.videos.get({ id: uuid })
253
254 expect(previousPaths).to.include(video.previewPath)
255 expect(previousPaths).to.include(video.thumbnailPath)
256
257 await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
258 await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
259 }
260 })
261 })
262
263 describe('Autoblacklist', function () {
264
265 function updateAutoBlacklist (enabled: boolean) {
266 return servers[0].config.updateExistingSubConfig({
267 newConfig: {
268 autoBlacklist: {
269 videos: {
270 ofUsers: {
271 enabled
272 }
273 }
274 }
275 }
276 })
277 }
278
279 async function expectBlacklist (uuid: string, value: boolean) {
280 const video = await servers[0].videos.getWithToken({ id: uuid })
281
282 expect(video.blacklisted).to.equal(value)
283 }
284
285 before(async function () {
286 await updateAutoBlacklist(true)
287 })
288
289 it('Should auto blacklist an unblacklisted video after file replacement', async function () {
290 this.timeout(120000)
291
292 const { uuid } = await servers[0].videos.quickUpload({ token: userToken, name: 'user video' })
293 await waitJobs(servers)
294 await expectBlacklist(uuid, true)
295
296 await servers[0].blacklist.remove({ videoId: uuid })
297 await expectBlacklist(uuid, false)
298
299 await servers[0].videos.replaceSourceFile({ videoId: uuid, token: userToken, fixture: 'video_short_360p.mp4' })
300 await waitJobs(servers)
301
302 await expectBlacklist(uuid, true)
303 })
304
305 it('Should auto blacklist an already blacklisted video after file replacement', async function () {
306 this.timeout(120000)
307
308 const { uuid } = await servers[0].videos.quickUpload({ token: userToken, name: 'user video' })
309 await waitJobs(servers)
310 await expectBlacklist(uuid, true)
311
312 await servers[0].videos.replaceSourceFile({ videoId: uuid, token: userToken, fixture: 'video_short_360p.mp4' })
313 await waitJobs(servers)
314
315 await expectBlacklist(uuid, true)
316 })
317
318 it('Should not auto blacklist if auto blacklist has been disabled between the upload and the replacement', async function () {
319 this.timeout(120000)
320
321 const { uuid } = await servers[0].videos.quickUpload({ token: userToken, name: 'user video' })
322 await waitJobs(servers)
323 await expectBlacklist(uuid, true)
324
325 await servers[0].blacklist.remove({ videoId: uuid })
326 await expectBlacklist(uuid, false)
327
328 await updateAutoBlacklist(false)
329
330 await servers[0].videos.replaceSourceFile({ videoId: uuid, token: userToken, fixture: 'video_short1.webm' })
331 await waitJobs(servers)
332
333 await expectBlacklist(uuid, false)
334 })
335 })
336
337 describe('With object storage enabled', function () {
338 if (areMockObjectStorageTestsDisabled()) return
339
340 const objectStorage = new ObjectStorageCommand()
341
342 before(async function () {
343 this.timeout(120000)
344
345 const configOverride = objectStorage.getDefaultMockConfig()
346 await objectStorage.prepareDefaultMockBuckets()
347
348 await servers[0].kill()
349 await servers[0].run(configOverride)
350 })
351
352 it('Should replace a video file with transcoding disabled', async function () {
353 this.timeout(120000)
354
355 await servers[0].config.disableTranscoding()
356
357 const { uuid } = await servers[0].videos.quickUpload({
358 name: 'object storage without transcoding',
359 fixture: 'video_short_720p.mp4'
360 })
361 await waitJobs(servers)
362
363 for (const server of servers) {
364 const video = await server.videos.get({ id: uuid })
365
366 const files = getAllFiles(video)
367 expect(files).to.have.lengthOf(1)
368 expect(files[0].resolution.id).to.equal(720)
369 expectStartWith(files[0].fileUrl, objectStorage.getMockWebVideosBaseUrl())
370 }
371
372 await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture: 'video_short_360p.mp4' })
373 await waitJobs(servers)
374
375 for (const server of servers) {
376 const video = await server.videos.get({ id: uuid })
377
378 const files = getAllFiles(video)
379 expect(files).to.have.lengthOf(1)
380 expect(files[0].resolution.id).to.equal(360)
381 expectStartWith(files[0].fileUrl, objectStorage.getMockWebVideosBaseUrl())
382 }
383 })
384
385 it('Should replace a video file with transcoding enabled', async function () {
386 this.timeout(120000)
387
388 const previousPaths: string[] = []
389
390 await servers[0].config.enableTranscoding({ hls: true, webVideo: true, with0p: true })
391
392 const { uuid: videoUUID } = await servers[0].videos.quickUpload({
393 name: 'object storage with transcoding',
394 fixture: 'video_short_360p.mp4'
395 })
396 uuid = videoUUID
397
398 await waitJobs(servers)
399
400 for (const server of servers) {
401 const video = await server.videos.get({ id: uuid })
402
403 const files = getAllFiles(video)
404 expect(files).to.have.lengthOf(4 * 2)
405
406 for (const file of files) {
407 previousPaths.push(file.fileUrl)
408 }
409
410 for (const file of video.files) {
411 expectStartWith(file.fileUrl, objectStorage.getMockWebVideosBaseUrl())
412 }
413
414 for (const file of video.streamingPlaylists[0].files) {
415 expectStartWith(file.fileUrl, objectStorage.getMockPlaylistBaseUrl())
416 }
417 }
418
419 await servers[0].videos.replaceSourceFile({ videoId: uuid, fixture: 'video_short_240p.mp4' })
420 await waitJobs(servers)
421
422 for (const server of servers) {
423 const video = await server.videos.get({ id: uuid })
424
425 const files = getAllFiles(video)
426 expect(files).to.have.lengthOf(3 * 2)
427
428 for (const file of files) {
429 expect(previousPaths).to.not.include(file.fileUrl)
430 }
431
432 for (const file of video.files) {
433 expectStartWith(file.fileUrl, objectStorage.getMockWebVideosBaseUrl())
434 }
435
436 for (const file of video.streamingPlaylists[0].files) {
437 expectStartWith(file.fileUrl, objectStorage.getMockPlaylistBaseUrl())
438 }
439 }
440 })
441 })
442 })
443
444 after(async function () {
445 await cleanupTests(servers)
446 })
447})
diff --git a/server/tests/api/videos/video-static-file-privacy.ts b/server/tests/api/videos/video-static-file-privacy.ts
deleted file mode 100644
index 0a9864134..000000000
--- a/server/tests/api/videos/video-static-file-privacy.ts
+++ /dev/null
@@ -1,600 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { decode } from 'magnet-uri'
5import { checkVideoFileTokenReinjection, expectStartWith, parseTorrentVideo } 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 PeerTubeServer,
14 sendRTMPStream,
15 setAccessTokensToServers,
16 setDefaultVideoChannel,
17 stopFfmpeg,
18 waitJobs
19} from '@shared/server-commands'
20
21describe('Test video static file privacy', function () {
22 let server: PeerTubeServer
23 let userToken: string
24
25 before(async function () {
26 this.timeout(50000)
27
28 server = await createSingleServer(1)
29 await setAccessTokensToServers([ server ])
30 await setDefaultVideoChannel([ server ])
31
32 userToken = await server.users.generateUserAndToken('user1')
33 })
34
35 describe('VOD static file path', function () {
36
37 function runSuite () {
38
39 async function checkPrivateFiles (uuid: string) {
40 const video = await server.videos.getWithToken({ id: uuid })
41
42 for (const file of video.files) {
43 expect(file.fileDownloadUrl).to.not.include('/private/')
44 expectStartWith(file.fileUrl, server.url + '/static/web-videos/private/')
45
46 const torrent = await parseTorrentVideo(server, file)
47 expect(torrent.urlList).to.have.lengthOf(0)
48
49 const magnet = decode(file.magnetUri)
50 expect(magnet.urlList).to.have.lengthOf(0)
51
52 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
53 }
54
55 const hls = video.streamingPlaylists[0]
56 if (hls) {
57 expectStartWith(hls.playlistUrl, server.url + '/static/streaming-playlists/hls/private/')
58 expectStartWith(hls.segmentsSha256Url, server.url + '/static/streaming-playlists/hls/private/')
59
60 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
61 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
62 }
63 }
64
65 async function checkPublicFiles (uuid: string) {
66 const video = await server.videos.get({ id: uuid })
67
68 for (const file of getAllFiles(video)) {
69 expect(file.fileDownloadUrl).to.not.include('/private/')
70 expect(file.fileUrl).to.not.include('/private/')
71
72 const torrent = await parseTorrentVideo(server, file)
73 expect(torrent.urlList[0]).to.not.include('private')
74
75 const magnet = decode(file.magnetUri)
76 expect(magnet.urlList[0]).to.not.include('private')
77
78 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
79 await makeRawRequest({ url: torrent.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
80 await makeRawRequest({ url: magnet.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
81 }
82
83 const hls = video.streamingPlaylists[0]
84 if (hls) {
85 expect(hls.playlistUrl).to.not.include('private')
86 expect(hls.segmentsSha256Url).to.not.include('private')
87
88 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
89 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
90 }
91 }
92
93 it('Should upload a private/internal/password protected video and have a private static path', async function () {
94 this.timeout(120000)
95
96 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
97 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy })
98 await waitJobs([ server ])
99
100 await checkPrivateFiles(uuid)
101 }
102
103 const { uuid } = await server.videos.quickUpload({
104 name: 'video',
105 privacy: VideoPrivacy.PASSWORD_PROTECTED,
106 videoPasswords: [ 'my super password' ]
107 })
108 await waitJobs([ server ])
109
110 await checkPrivateFiles(uuid)
111 })
112
113 it('Should upload a public video and update it as private/internal to have a private static path', async function () {
114 this.timeout(120000)
115
116 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
117 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PUBLIC })
118 await waitJobs([ server ])
119
120 await server.videos.update({ id: uuid, attributes: { privacy } })
121 await waitJobs([ server ])
122
123 await checkPrivateFiles(uuid)
124 }
125 })
126
127 it('Should upload a private video and update it to unlisted to have a public static path', async function () {
128 this.timeout(120000)
129
130 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
131 await waitJobs([ server ])
132
133 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
134 await waitJobs([ server ])
135
136 await checkPublicFiles(uuid)
137 })
138
139 it('Should upload an internal video and update it to public to have a public static path', async function () {
140 this.timeout(120000)
141
142 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
143 await waitJobs([ server ])
144
145 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
146 await waitJobs([ server ])
147
148 await checkPublicFiles(uuid)
149 })
150
151 it('Should upload an internal video and schedule a public publish', async function () {
152 this.timeout(120000)
153
154 const attributes = {
155 name: 'video',
156 privacy: VideoPrivacy.PRIVATE,
157 scheduleUpdate: {
158 updateAt: new Date(Date.now() + 1000).toISOString(),
159 privacy: VideoPrivacy.PUBLIC as VideoPrivacy.PUBLIC
160 }
161 }
162
163 const { uuid } = await server.videos.upload({ attributes })
164
165 await waitJobs([ server ])
166 await wait(1000)
167 await server.debug.sendCommand({ body: { command: 'process-update-videos-scheduler' } })
168
169 await waitJobs([ server ])
170
171 await checkPublicFiles(uuid)
172 })
173 }
174
175 describe('Without transcoding', function () {
176 runSuite()
177 })
178
179 describe('With transcoding', function () {
180
181 before(async function () {
182 await server.config.enableMinimumTranscoding()
183 })
184
185 runSuite()
186 })
187 })
188
189 describe('VOD static file right check', function () {
190 let unrelatedFileToken: string
191
192 async function checkVideoFiles (options: {
193 id: string
194 expectedStatus: HttpStatusCode
195 token: string
196 videoFileToken: string
197 videoPassword?: string
198 }) {
199 const { id, expectedStatus, token, videoFileToken, videoPassword } = options
200
201 const video = await server.videos.getWithToken({ id })
202
203 for (const file of getAllFiles(video)) {
204 await makeRawRequest({ url: file.fileUrl, token, expectedStatus })
205 await makeRawRequest({ url: file.fileDownloadUrl, token, expectedStatus })
206
207 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken }, expectedStatus })
208 await makeRawRequest({ url: file.fileDownloadUrl, query: { videoFileToken }, expectedStatus })
209
210 if (videoPassword) {
211 const headers = { 'x-peertube-video-password': videoPassword }
212 await makeRawRequest({ url: file.fileUrl, headers, expectedStatus })
213 await makeRawRequest({ url: file.fileDownloadUrl, headers, expectedStatus })
214 }
215 }
216
217 const hls = video.streamingPlaylists[0]
218 await makeRawRequest({ url: hls.playlistUrl, token, expectedStatus })
219 await makeRawRequest({ url: hls.segmentsSha256Url, token, expectedStatus })
220
221 await makeRawRequest({ url: hls.playlistUrl, query: { videoFileToken }, expectedStatus })
222 await makeRawRequest({ url: hls.segmentsSha256Url, query: { videoFileToken }, expectedStatus })
223
224 if (videoPassword) {
225 const headers = { 'x-peertube-video-password': videoPassword }
226 await makeRawRequest({ url: hls.playlistUrl, token: null, headers, expectedStatus })
227 await makeRawRequest({ url: hls.segmentsSha256Url, token: null, headers, expectedStatus })
228 }
229 }
230
231 before(async function () {
232 await server.config.enableMinimumTranscoding()
233
234 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
235 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
236 })
237
238 it('Should not be able to access a private video files without OAuth token and file token', async function () {
239 this.timeout(120000)
240
241 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
242 await waitJobs([ server ])
243
244 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: null, videoFileToken: null })
245 })
246
247 it('Should not be able to access password protected video files without OAuth token, file token and password', async function () {
248 this.timeout(120000)
249 const videoPassword = 'my super password'
250
251 const { uuid } = await server.videos.quickUpload({
252 name: 'password protected video',
253 privacy: VideoPrivacy.PASSWORD_PROTECTED,
254 videoPasswords: [ videoPassword ]
255 })
256 await waitJobs([ server ])
257
258 await checkVideoFiles({
259 id: uuid,
260 expectedStatus: HttpStatusCode.FORBIDDEN_403,
261 token: null,
262 videoFileToken: null,
263 videoPassword: null
264 })
265 })
266
267 it('Should not be able to access an password video files with incorrect OAuth token, file token and password', async function () {
268 this.timeout(120000)
269 const videoPassword = 'my super password'
270
271 const { uuid } = await server.videos.quickUpload({
272 name: 'password protected video',
273 privacy: VideoPrivacy.PASSWORD_PROTECTED,
274 videoPasswords: [ videoPassword ]
275 })
276 await waitJobs([ server ])
277
278 await checkVideoFiles({
279 id: uuid,
280 expectedStatus: HttpStatusCode.FORBIDDEN_403,
281 token: userToken,
282 videoFileToken: unrelatedFileToken,
283 videoPassword: 'incorrectPassword'
284 })
285 })
286
287 it('Should not be able to access an private video files without appropriate OAuth token and file token', async function () {
288 this.timeout(120000)
289
290 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
291 await waitJobs([ server ])
292
293 await checkVideoFiles({
294 id: uuid,
295 expectedStatus: HttpStatusCode.FORBIDDEN_403,
296 token: userToken,
297 videoFileToken: unrelatedFileToken
298 })
299 })
300
301 it('Should be able to access a private video files with appropriate OAuth token or file token', async function () {
302 this.timeout(120000)
303
304 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
305 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
306
307 await waitJobs([ server ])
308
309 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
310 })
311
312 it('Should be able to access a password protected video files with appropriate OAuth token or file token', async function () {
313 this.timeout(120000)
314 const videoPassword = 'my super password'
315
316 const { uuid } = await server.videos.quickUpload({
317 name: 'video',
318 privacy: VideoPrivacy.PASSWORD_PROTECTED,
319 videoPasswords: [ videoPassword ]
320 })
321
322 const videoFileToken = await server.videoToken.getVideoFileToken({ token: null, videoId: uuid, videoPassword })
323
324 await waitJobs([ server ])
325
326 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken, videoPassword })
327 })
328
329 it('Should reinject video file token', async function () {
330 this.timeout(120000)
331
332 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
333
334 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
335 await waitJobs([ server ])
336
337 {
338 const video = await server.videos.getWithToken({ id: uuid })
339 const hls = video.streamingPlaylists[0]
340 const query = { videoFileToken }
341 const { text } = await makeRawRequest({ url: hls.playlistUrl, query, expectedStatus: HttpStatusCode.OK_200 })
342
343 expect(text).to.not.include(videoFileToken)
344 }
345
346 {
347 await checkVideoFileTokenReinjection({
348 server,
349 videoUUID: uuid,
350 videoFileToken,
351 resolutions: [ 240, 720 ],
352 isLive: false
353 })
354 }
355 })
356
357 it('Should be able to access a private video of another user with an admin OAuth token or file token', async function () {
358 this.timeout(120000)
359
360 const { uuid } = await server.videos.quickUpload({ name: 'video', token: userToken, privacy: VideoPrivacy.PRIVATE })
361 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
362
363 await waitJobs([ server ])
364
365 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
366 })
367 })
368
369 describe('Live static file path and check', function () {
370 let normalLiveId: string
371 let normalLive: LiveVideo
372
373 let permanentLiveId: string
374 let permanentLive: LiveVideo
375
376 let passwordProtectedLiveId: string
377 let passwordProtectedLive: LiveVideo
378
379 const correctPassword = 'my super password'
380
381 let unrelatedFileToken: string
382
383 async function checkLiveFiles (options: { live: LiveVideo, liveId: string, videoPassword?: string }) {
384 const { live, liveId, videoPassword } = options
385 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
386 await server.live.waitUntilPublished({ videoId: liveId })
387
388 const video = await server.videos.getWithToken({ id: liveId })
389
390 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
391
392 const hls = video.streamingPlaylists[0]
393
394 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
395 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
396
397 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
398 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
399
400 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
401 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
402 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
403
404 if (videoPassword) {
405 await makeRawRequest({ url, headers: { 'x-peertube-video-password': videoPassword }, expectedStatus: HttpStatusCode.OK_200 })
406 await makeRawRequest({
407 url,
408 headers: { 'x-peertube-video-password': 'incorrectPassword' },
409 expectedStatus: HttpStatusCode.FORBIDDEN_403
410 })
411 }
412
413 }
414
415 await stopFfmpeg(ffmpegCommand)
416 }
417
418 async function checkReplay (replay: VideoDetails) {
419 const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
420
421 const hls = replay.streamingPlaylists[0]
422 expect(hls.files).to.not.have.lengthOf(0)
423
424 for (const file of hls.files) {
425 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
426 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
427
428 await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
429 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
430 await makeRawRequest({
431 url: file.fileUrl,
432 query: { videoFileToken: unrelatedFileToken },
433 expectedStatus: HttpStatusCode.FORBIDDEN_403
434 })
435 }
436
437 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
438 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
439
440 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
441 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
442
443 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
444 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
445 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
446 }
447 }
448
449 before(async function () {
450 await server.config.enableMinimumTranscoding()
451
452 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
453 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
454
455 await server.config.enableLive({
456 allowReplay: true,
457 transcoding: true,
458 resolutions: 'min'
459 })
460
461 {
462 const { video, live } = await server.live.quickCreate({
463 saveReplay: true,
464 permanentLive: false,
465 privacy: VideoPrivacy.PRIVATE
466 })
467 normalLiveId = video.uuid
468 normalLive = live
469 }
470
471 {
472 const { video, live } = await server.live.quickCreate({
473 saveReplay: true,
474 permanentLive: true,
475 privacy: VideoPrivacy.PRIVATE
476 })
477 permanentLiveId = video.uuid
478 permanentLive = live
479 }
480
481 {
482 const { video, live } = await server.live.quickCreate({
483 saveReplay: false,
484 permanentLive: false,
485 privacy: VideoPrivacy.PASSWORD_PROTECTED,
486 videoPasswords: [ correctPassword ]
487 })
488 passwordProtectedLiveId = video.uuid
489 passwordProtectedLive = live
490 }
491 })
492
493 it('Should create a private normal live and have a private static path', async function () {
494 this.timeout(240000)
495
496 await checkLiveFiles({ live: normalLive, liveId: normalLiveId })
497 })
498
499 it('Should create a private permanent live and have a private static path', async function () {
500 this.timeout(240000)
501
502 await checkLiveFiles({ live: permanentLive, liveId: permanentLiveId })
503 })
504
505 it('Should create a password protected live and have a private static path', async function () {
506 this.timeout(240000)
507
508 await checkLiveFiles({ live: passwordProtectedLive, liveId: passwordProtectedLiveId, videoPassword: correctPassword })
509 })
510
511 it('Should reinject video file token on permanent live', async function () {
512 this.timeout(240000)
513
514 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: permanentLive.rtmpUrl, streamKey: permanentLive.streamKey })
515 await server.live.waitUntilPublished({ videoId: permanentLiveId })
516
517 const video = await server.videos.getWithToken({ id: permanentLiveId })
518 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
519 const hls = video.streamingPlaylists[0]
520
521 {
522 const query = { videoFileToken }
523 const { text } = await makeRawRequest({ url: hls.playlistUrl, query, expectedStatus: HttpStatusCode.OK_200 })
524
525 expect(text).to.not.include(videoFileToken)
526 }
527
528 {
529 await checkVideoFileTokenReinjection({
530 server,
531 videoUUID: permanentLiveId,
532 videoFileToken,
533 resolutions: [ 720 ],
534 isLive: true
535 })
536 }
537
538 await stopFfmpeg(ffmpegCommand)
539 })
540
541 it('Should have created a replay of the normal live with a private static path', async function () {
542 this.timeout(240000)
543
544 await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
545
546 const replay = await server.videos.getWithToken({ id: normalLiveId })
547 await checkReplay(replay)
548 })
549
550 it('Should have created a replay of the permanent live with a private static path', async function () {
551 this.timeout(240000)
552
553 await server.live.waitUntilWaiting({ videoId: permanentLiveId })
554 await waitJobs([ server ])
555
556 const live = await server.videos.getWithToken({ id: permanentLiveId })
557 const replayFromList = await findExternalSavedVideo(server, live)
558 const replay = await server.videos.getWithToken({ id: replayFromList.id })
559
560 await checkReplay(replay)
561 })
562 })
563
564 describe('With static file right check disabled', function () {
565 let videoUUID: string
566
567 before(async function () {
568 this.timeout(240000)
569
570 await server.kill()
571
572 await server.run({
573 static_files: {
574 private_files_require_auth: false
575 }
576 })
577
578 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
579 videoUUID = uuid
580
581 await waitJobs([ server ])
582 })
583
584 it('Should not check auth for private static files', async function () {
585 const video = await server.videos.getWithToken({ id: videoUUID })
586
587 for (const file of getAllFiles(video)) {
588 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
589 }
590
591 const hls = video.streamingPlaylists[0]
592 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
593 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
594 })
595 })
596
597 after(async function () {
598 await cleanupTests([ server ])
599 })
600})
diff --git a/server/tests/api/videos/video-storyboard.ts b/server/tests/api/videos/video-storyboard.ts
deleted file mode 100644
index 07f371cad..000000000
--- a/server/tests/api/videos/video-storyboard.ts
+++ /dev/null
@@ -1,213 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { readdir } from 'fs-extra'
5import { basename } from 'path'
6import { FIXTURE_URLS } from '@server/tests/shared'
7import { areHttpImportTestsDisabled } from '@shared/core-utils'
8import { HttpStatusCode, VideoPrivacy } from '@shared/models'
9import {
10 cleanupTests,
11 createMultipleServers,
12 doubleFollow,
13 makeGetRequest,
14 PeerTubeServer,
15 sendRTMPStream,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 stopFfmpeg,
19 waitJobs
20} from '@shared/server-commands'
21
22async function checkStoryboard (options: {
23 server: PeerTubeServer
24 uuid: string
25 tilesCount?: number
26 minSize?: number
27}) {
28 const { server, uuid, tilesCount, minSize = 1000 } = options
29
30 const { storyboards } = await server.storyboard.list({ id: uuid })
31
32 expect(storyboards).to.have.lengthOf(1)
33
34 const storyboard = storyboards[0]
35
36 expect(storyboard.spriteDuration).to.equal(1)
37 expect(storyboard.spriteHeight).to.equal(108)
38 expect(storyboard.spriteWidth).to.equal(192)
39 expect(storyboard.storyboardPath).to.exist
40
41 if (tilesCount) {
42 expect(storyboard.totalWidth).to.equal(192 * Math.min(tilesCount, 10))
43 expect(storyboard.totalHeight).to.equal(108 * Math.max((tilesCount / 10), 1))
44 }
45
46 const { body } = await makeGetRequest({ url: server.url, path: storyboard.storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
47 expect(body.length).to.be.above(minSize)
48}
49
50describe('Test video storyboard', function () {
51 let servers: PeerTubeServer[]
52
53 let baseUUID: string
54
55 before(async function () {
56 this.timeout(120000)
57
58 servers = await createMultipleServers(2)
59 await setAccessTokensToServers(servers)
60 await setDefaultVideoChannel(servers)
61
62 await doubleFollow(servers[0], servers[1])
63 })
64
65 it('Should generate a storyboard after upload without transcoding', async function () {
66 this.timeout(120000)
67
68 // 5s video
69 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_short.webm' })
70 baseUUID = uuid
71 await waitJobs(servers)
72
73 for (const server of servers) {
74 await checkStoryboard({ server, uuid, tilesCount: 5 })
75 }
76 })
77
78 it('Should generate a storyboard after upload without transcoding with a long video', async function () {
79 this.timeout(120000)
80
81 // 124s video
82 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_very_long_10p.mp4' })
83 await waitJobs(servers)
84
85 for (const server of servers) {
86 await checkStoryboard({ server, uuid, tilesCount: 100 })
87 }
88 })
89
90 it('Should generate a storyboard after upload with transcoding', async function () {
91 this.timeout(120000)
92
93 await servers[0].config.enableMinimumTranscoding()
94
95 // 5s video
96 const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_short.webm' })
97 await waitJobs(servers)
98
99 for (const server of servers) {
100 await checkStoryboard({ server, uuid, tilesCount: 5 })
101 }
102 })
103
104 it('Should generate a storyboard after an audio upload', async function () {
105 this.timeout(120000)
106
107 // 6s audio
108 const attributes = { name: 'audio', fixture: 'sample.ogg' }
109 const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' })
110 await waitJobs(servers)
111
112 for (const server of servers) {
113 try {
114 await checkStoryboard({ server, uuid, tilesCount: 6, minSize: 250 })
115 } catch { // FIXME: to remove after ffmpeg CI upgrade, ffmpeg CI version (4.3) generates a 7.6s length video
116 await checkStoryboard({ server, uuid, tilesCount: 8, minSize: 250 })
117 }
118 }
119 })
120
121 it('Should generate a storyboard after HTTP import', async function () {
122 this.timeout(120000)
123
124 if (areHttpImportTestsDisabled()) return
125
126 // 3s video
127 const { video } = await servers[0].imports.importVideo({
128 attributes: {
129 targetUrl: FIXTURE_URLS.goodVideo,
130 channelId: servers[0].store.channel.id,
131 privacy: VideoPrivacy.PUBLIC
132 }
133 })
134 await waitJobs(servers)
135
136 for (const server of servers) {
137 await checkStoryboard({ server, uuid: video.uuid, tilesCount: 3 })
138 }
139 })
140
141 it('Should generate a storyboard after torrent import', async function () {
142 this.timeout(120000)
143
144 if (areHttpImportTestsDisabled()) return
145
146 // 10s video
147 const { video } = await servers[0].imports.importVideo({
148 attributes: {
149 magnetUri: FIXTURE_URLS.magnet,
150 channelId: servers[0].store.channel.id,
151 privacy: VideoPrivacy.PUBLIC
152 }
153 })
154 await waitJobs(servers)
155
156 for (const server of servers) {
157 await checkStoryboard({ server, uuid: video.uuid, tilesCount: 10 })
158 }
159 })
160
161 it('Should generate a storyboard after a live', async function () {
162 this.timeout(240000)
163
164 await servers[0].config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
165
166 const { live, video } = await servers[0].live.quickCreate({
167 saveReplay: true,
168 permanentLive: false,
169 privacy: VideoPrivacy.PUBLIC
170 })
171
172 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
173 await servers[0].live.waitUntilPublished({ videoId: video.id })
174
175 await stopFfmpeg(ffmpegCommand)
176
177 await servers[0].live.waitUntilReplacedByReplay({ videoId: video.id })
178 await waitJobs(servers)
179
180 for (const server of servers) {
181 await checkStoryboard({ server, uuid: video.uuid })
182 }
183 })
184
185 it('Should cleanup storyboards on video deletion', async function () {
186 this.timeout(60000)
187
188 const { storyboards } = await servers[0].storyboard.list({ id: baseUUID })
189 const storyboardName = basename(storyboards[0].storyboardPath)
190
191 const listFiles = () => {
192 const storyboardPath = servers[0].getDirectoryPath('storyboards')
193 return readdir(storyboardPath)
194 }
195
196 {
197 const storyboads = await listFiles()
198 expect(storyboads).to.include(storyboardName)
199 }
200
201 await servers[0].videos.remove({ id: baseUUID })
202 await waitJobs(servers)
203
204 {
205 const storyboads = await listFiles()
206 expect(storyboads).to.not.include(storyboardName)
207 }
208 })
209
210 after(async function () {
211 await cleanupTests(servers)
212 })
213})
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
deleted file mode 100644
index 48de7c537..000000000
--- a/server/tests/api/videos/videos-common-filters.ts
+++ /dev/null
@@ -1,489 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pick } from '@shared/core-utils'
5import { HttpStatusCode, UserRole, Video, VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 makeGetRequest,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 setDefaultAccountAvatar,
14 setDefaultVideoChannel,
15 waitJobs
16} from '@shared/server-commands'
17
18describe('Test videos filter', function () {
19 let servers: PeerTubeServer[]
20 let paths: string[]
21 let remotePaths: string[]
22
23 const subscriptionVideosPath = '/api/v1/users/me/subscriptions/videos'
24
25 // ---------------------------------------------------------------
26
27 before(async function () {
28 this.timeout(240000)
29
30 servers = await createMultipleServers(2)
31
32 await setAccessTokensToServers(servers)
33 await setDefaultVideoChannel(servers)
34 await setDefaultAccountAvatar(servers)
35
36 await servers[1].config.enableMinimumTranscoding()
37
38 for (const server of servers) {
39 const moderator = { username: 'moderator', password: 'my super password' }
40 await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
41 server['moderatorAccessToken'] = await server.login.getAccessToken(moderator)
42
43 await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } })
44
45 {
46 const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED }
47 await server.videos.upload({ attributes })
48 }
49
50 {
51 const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
52 await server.videos.upload({ attributes })
53 }
54
55 // Subscribing to itself
56 await server.subscriptions.add({ targetUri: 'root_channel@' + server.host })
57 }
58
59 await doubleFollow(servers[0], servers[1])
60
61 paths = [
62 `/api/v1/video-channels/root_channel/videos`,
63 `/api/v1/accounts/root/videos`,
64 '/api/v1/videos',
65 '/api/v1/search/videos',
66 subscriptionVideosPath
67 ]
68
69 remotePaths = [
70 `/api/v1/video-channels/root_channel@${servers[1].host}/videos`,
71 `/api/v1/accounts/root@${servers[1].host}/videos`,
72 '/api/v1/videos',
73 '/api/v1/search/videos'
74 ]
75 })
76
77 describe('Check videos filters', function () {
78
79 async function listVideos (options: {
80 server: PeerTubeServer
81 path: string
82 isLocal?: boolean
83 hasWebVideoFiles?: boolean
84 hasHLSFiles?: boolean
85 include?: VideoInclude
86 privacyOneOf?: VideoPrivacy[]
87 category?: number
88 tagsAllOf?: string[]
89 token?: string
90 expectedStatus?: HttpStatusCode
91 excludeAlreadyWatched?: boolean
92 }) {
93 const res = await makeGetRequest({
94 url: options.server.url,
95 path: options.path,
96 token: options.token ?? options.server.accessToken,
97 query: {
98 ...pick(options, [
99 'isLocal',
100 'include',
101 'category',
102 'tagsAllOf',
103 'hasWebVideoFiles',
104 'hasHLSFiles',
105 'privacyOneOf',
106 'excludeAlreadyWatched'
107 ]),
108
109 sort: 'createdAt'
110 },
111 expectedStatus: options.expectedStatus ?? HttpStatusCode.OK_200
112 })
113
114 return res.body.data as Video[]
115 }
116
117 async function getVideosNames (
118 options: {
119 server: PeerTubeServer
120 isLocal?: boolean
121 include?: VideoInclude
122 privacyOneOf?: VideoPrivacy[]
123 token?: string
124 expectedStatus?: HttpStatusCode
125 skipSubscription?: boolean
126 excludeAlreadyWatched?: boolean
127 }
128 ) {
129 const { skipSubscription = false } = options
130 const videosResults: string[][] = []
131
132 for (const path of paths) {
133 if (skipSubscription && path === subscriptionVideosPath) continue
134
135 const videos = await listVideos({ ...options, path })
136
137 videosResults.push(videos.map(v => v.name))
138 }
139
140 return videosResults
141 }
142
143 it('Should display local videos', async function () {
144 for (const server of servers) {
145 const namesResults = await getVideosNames({ server, isLocal: true })
146
147 for (const names of namesResults) {
148 expect(names).to.have.lengthOf(1)
149 expect(names[0]).to.equal('public ' + server.serverNumber)
150 }
151 }
152 })
153
154 it('Should display local videos with hidden privacy by the admin or the moderator', async function () {
155 for (const server of servers) {
156 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
157
158 const namesResults = await getVideosNames(
159 {
160 server,
161 token,
162 isLocal: true,
163 privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ],
164 skipSubscription: true
165 }
166 )
167
168 for (const names of namesResults) {
169 expect(names).to.have.lengthOf(3)
170
171 expect(names[0]).to.equal('public ' + server.serverNumber)
172 expect(names[1]).to.equal('unlisted ' + server.serverNumber)
173 expect(names[2]).to.equal('private ' + server.serverNumber)
174 }
175 }
176 }
177 })
178
179 it('Should display all videos by the admin or the moderator', async function () {
180 for (const server of servers) {
181 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
182
183 const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({
184 server,
185 token,
186 privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ]
187 })
188
189 expect(channelVideos).to.have.lengthOf(3)
190 expect(accountVideos).to.have.lengthOf(3)
191
192 expect(videos).to.have.lengthOf(5)
193 expect(searchVideos).to.have.lengthOf(5)
194 }
195 }
196 })
197
198 it('Should display only remote videos', async function () {
199 this.timeout(120000)
200
201 await servers[1].videos.upload({ attributes: { name: 'remote video' } })
202
203 await waitJobs(servers)
204
205 const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
206
207 for (const path of remotePaths) {
208 {
209 const videos = await listVideos({ server: servers[0], path })
210 const video = finder(videos)
211 expect(video).to.exist
212 }
213
214 {
215 const videos = await listVideos({ server: servers[0], path, isLocal: false })
216 const video = finder(videos)
217 expect(video).to.exist
218 }
219
220 {
221 const videos = await listVideos({ server: servers[0], path, isLocal: true })
222 const video = finder(videos)
223 expect(video).to.not.exist
224 }
225 }
226 })
227
228 it('Should include not published videos', async function () {
229 await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
230 await servers[0].live.create({ fields: { name: 'live video', channelId: servers[0].store.channel.id, privacy: VideoPrivacy.PUBLIC } })
231
232 const finder = (videos: Video[]) => videos.find(v => v.name === 'live video')
233
234 for (const path of paths) {
235 {
236 const videos = await listVideos({ server: servers[0], path })
237 const video = finder(videos)
238 expect(video).to.not.exist
239 expect(videos[0].state).to.not.exist
240 expect(videos[0].waitTranscoding).to.not.exist
241 }
242
243 {
244 const videos = await listVideos({ server: servers[0], path, include: VideoInclude.NOT_PUBLISHED_STATE })
245 const video = finder(videos)
246 expect(video).to.exist
247 expect(video.state).to.exist
248 }
249 }
250 })
251
252 it('Should include blacklisted videos', async function () {
253 const { id } = await servers[0].videos.upload({ attributes: { name: 'blacklisted' } })
254
255 await servers[0].blacklist.add({ videoId: id })
256
257 const finder = (videos: Video[]) => videos.find(v => v.name === 'blacklisted')
258
259 for (const path of paths) {
260 {
261 const videos = await listVideos({ server: servers[0], path })
262 const video = finder(videos)
263 expect(video).to.not.exist
264 expect(videos[0].blacklisted).to.not.exist
265 }
266
267 {
268 const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLACKLISTED })
269 const video = finder(videos)
270 expect(video).to.exist
271 expect(video.blacklisted).to.be.true
272 }
273 }
274 })
275
276 it('Should include videos from muted account', async function () {
277 const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
278
279 await servers[0].blocklist.addToServerBlocklist({ account: 'root@' + servers[1].host })
280
281 for (const path of remotePaths) {
282 {
283 const videos = await listVideos({ server: servers[0], path })
284 const video = finder(videos)
285 expect(video).to.not.exist
286
287 // Some paths won't have videos
288 if (videos[0]) {
289 expect(videos[0].blockedOwner).to.not.exist
290 expect(videos[0].blockedServer).to.not.exist
291 }
292 }
293
294 {
295 const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
296
297 const video = finder(videos)
298 expect(video).to.exist
299 expect(video.blockedServer).to.be.false
300 expect(video.blockedOwner).to.be.true
301 }
302 }
303
304 await servers[0].blocklist.removeFromServerBlocklist({ account: 'root@' + servers[1].host })
305 })
306
307 it('Should include videos from muted server', async function () {
308 const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
309
310 await servers[0].blocklist.addToServerBlocklist({ server: servers[1].host })
311
312 for (const path of remotePaths) {
313 {
314 const videos = await listVideos({ server: servers[0], path })
315 const video = finder(videos)
316 expect(video).to.not.exist
317
318 // Some paths won't have videos
319 if (videos[0]) {
320 expect(videos[0].blockedOwner).to.not.exist
321 expect(videos[0].blockedServer).to.not.exist
322 }
323 }
324
325 {
326 const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
327 const video = finder(videos)
328 expect(video).to.exist
329 expect(video.blockedServer).to.be.true
330 expect(video.blockedOwner).to.be.false
331 }
332 }
333
334 await servers[0].blocklist.removeFromServerBlocklist({ server: servers[1].host })
335 })
336
337 it('Should include video files', async function () {
338 for (const path of paths) {
339 {
340 const videos = await listVideos({ server: servers[0], path })
341
342 for (const video of videos) {
343 const videoWithFiles = video as VideoDetails
344
345 expect(videoWithFiles.files).to.not.exist
346 expect(videoWithFiles.streamingPlaylists).to.not.exist
347 }
348 }
349
350 {
351 const videos = await listVideos({ server: servers[0], path, include: VideoInclude.FILES })
352
353 for (const video of videos) {
354 const videoWithFiles = video as VideoDetails
355
356 expect(videoWithFiles.files).to.exist
357 expect(videoWithFiles.files).to.have.length.at.least(1)
358 }
359 }
360 }
361 })
362
363 it('Should filter by tags and category', async function () {
364 await servers[0].videos.upload({ attributes: { name: 'tag filter', tags: [ 'tag1', 'tag2' ] } })
365 await servers[0].videos.upload({ attributes: { name: 'tag filter with category', tags: [ 'tag3' ], category: 4 } })
366
367 for (const path of paths) {
368 {
369 const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag2' ] })
370 expect(videos).to.have.lengthOf(1)
371 expect(videos[0].name).to.equal('tag filter')
372 }
373
374 {
375 const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag3' ] })
376 expect(videos).to.have.lengthOf(0)
377 }
378
379 {
380 const { data, total } = await servers[0].videos.list({ tagsAllOf: [ 'tag3' ], categoryOneOf: [ 4 ] })
381 expect(total).to.equal(1)
382 expect(data[0].name).to.equal('tag filter with category')
383 }
384
385 {
386 const { total } = await servers[0].videos.list({ tagsAllOf: [ 'tag4' ], categoryOneOf: [ 4 ] })
387 expect(total).to.equal(0)
388 }
389 }
390 })
391
392 it('Should filter by HLS or Web Video files', async function () {
393 this.timeout(360000)
394
395 const finderFactory = (name: string) => (videos: Video[]) => videos.some(v => v.name === name)
396
397 await servers[0].config.enableTranscoding({ hls: false, webVideo: true })
398 await servers[0].videos.upload({ attributes: { name: 'web video' } })
399 const hasWebVideo = finderFactory('web video')
400
401 await waitJobs(servers)
402
403 await servers[0].config.enableTranscoding({ hls: true, webVideo: false })
404 await servers[0].videos.upload({ attributes: { name: 'hls video' } })
405 const hasHLS = finderFactory('hls video')
406
407 await waitJobs(servers)
408
409 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
410 await servers[0].videos.upload({ attributes: { name: 'hls and web video' } })
411 const hasBoth = finderFactory('hls and web video')
412
413 await waitJobs(servers)
414
415 for (const path of paths) {
416 {
417 const videos = await listVideos({ server: servers[0], path, hasWebVideoFiles: true })
418
419 expect(hasWebVideo(videos)).to.be.true
420 expect(hasHLS(videos)).to.be.false
421 expect(hasBoth(videos)).to.be.true
422 }
423
424 {
425 const videos = await listVideos({ server: servers[0], path, hasWebVideoFiles: false })
426
427 expect(hasWebVideo(videos)).to.be.false
428 expect(hasHLS(videos)).to.be.true
429 expect(hasBoth(videos)).to.be.false
430 }
431
432 {
433 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true })
434
435 expect(hasWebVideo(videos)).to.be.false
436 expect(hasHLS(videos)).to.be.true
437 expect(hasBoth(videos)).to.be.true
438 }
439
440 {
441 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false })
442
443 expect(hasWebVideo(videos)).to.be.true
444 expect(hasHLS(videos)).to.be.false
445 expect(hasBoth(videos)).to.be.false
446 }
447
448 {
449 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false, hasWebVideoFiles: false })
450
451 expect(hasWebVideo(videos)).to.be.false
452 expect(hasHLS(videos)).to.be.false
453 expect(hasBoth(videos)).to.be.false
454 }
455
456 {
457 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true, hasWebVideoFiles: true })
458
459 expect(hasWebVideo(videos)).to.be.false
460 expect(hasHLS(videos)).to.be.false
461 expect(hasBoth(videos)).to.be.true
462 }
463 }
464 })
465
466 it('Should filter already watched videos by the user', async function () {
467 const { id } = await servers[0].videos.upload({ attributes: { name: 'video for history' } })
468
469 for (const path of paths) {
470 const videos = await listVideos({ server: servers[0], path, isLocal: true, excludeAlreadyWatched: true })
471 const foundVideo = videos.find(video => video.id === id)
472
473 expect(foundVideo).to.not.be.undefined
474 }
475 await servers[0].views.view({ id, currentTime: 1, token: servers[0].accessToken })
476
477 for (const path of paths) {
478 const videos = await listVideos({ server: servers[0], path, excludeAlreadyWatched: true })
479 const foundVideo = videos.find(video => video.id === id)
480
481 expect(foundVideo).to.be.undefined
482 }
483 })
484 })
485
486 after(async function () {
487 await cleanupTests(servers)
488 })
489})
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts
deleted file mode 100644
index 6df26ab7d..000000000
--- a/server/tests/api/videos/videos-history.ts
+++ /dev/null
@@ -1,224 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { Video } from '@shared/models'
6import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7
8describe('Test videos history', function () {
9 let server: PeerTubeServer = null
10 let video1Id: number
11 let video1UUID: string
12 let video2UUID: string
13 let video3UUID: string
14 let video3WatchedDate: Date
15 let userAccessToken: string
16
17 before(async function () {
18 this.timeout(120000)
19
20 server = await createSingleServer(1)
21
22 await setAccessTokensToServers([ server ])
23
24 // 10 seconds long
25 const fixture = 'video_short1.webm'
26
27 {
28 const { id, uuid } = await server.videos.upload({ attributes: { name: 'video 1', fixture } })
29 video1UUID = uuid
30 video1Id = id
31 }
32
33 {
34 const { uuid } = await server.videos.upload({ attributes: { name: 'video 2', fixture } })
35 video2UUID = uuid
36 }
37
38 {
39 const { uuid } = await server.videos.upload({ attributes: { name: 'video 3', fixture } })
40 video3UUID = uuid
41 }
42
43 userAccessToken = await server.users.generateUserAndToken('user_1')
44 })
45
46 it('Should get videos, without watching history', async function () {
47 const { data } = await server.videos.listWithToken()
48
49 for (const video of data) {
50 const videoDetails = await server.videos.getWithToken({ id: video.id })
51
52 expect(video.userHistory).to.be.undefined
53 expect(videoDetails.userHistory).to.be.undefined
54 }
55 })
56
57 it('Should watch the first and second video', async function () {
58 await server.views.view({ id: video2UUID, token: server.accessToken, currentTime: 8 })
59 await server.views.view({ id: video1UUID, token: server.accessToken, currentTime: 3 })
60 })
61
62 it('Should return the correct history when listing, searching and getting videos', async function () {
63 const videosOfVideos: Video[][] = []
64
65 {
66 const { data } = await server.videos.listWithToken()
67 videosOfVideos.push(data)
68 }
69
70 {
71 const body = await server.search.searchVideos({ token: server.accessToken, search: 'video' })
72 videosOfVideos.push(body.data)
73 }
74
75 for (const videos of videosOfVideos) {
76 const video1 = videos.find(v => v.uuid === video1UUID)
77 const video2 = videos.find(v => v.uuid === video2UUID)
78 const video3 = videos.find(v => v.uuid === video3UUID)
79
80 expect(video1.userHistory).to.not.be.undefined
81 expect(video1.userHistory.currentTime).to.equal(3)
82
83 expect(video2.userHistory).to.not.be.undefined
84 expect(video2.userHistory.currentTime).to.equal(8)
85
86 expect(video3.userHistory).to.be.undefined
87 }
88
89 {
90 const videoDetails = await server.videos.getWithToken({ id: video1UUID })
91
92 expect(videoDetails.userHistory).to.not.be.undefined
93 expect(videoDetails.userHistory.currentTime).to.equal(3)
94 }
95
96 {
97 const videoDetails = await server.videos.getWithToken({ id: video2UUID })
98
99 expect(videoDetails.userHistory).to.not.be.undefined
100 expect(videoDetails.userHistory.currentTime).to.equal(8)
101 }
102
103 {
104 const videoDetails = await server.videos.getWithToken({ id: video3UUID })
105
106 expect(videoDetails.userHistory).to.be.undefined
107 }
108 })
109
110 it('Should have these videos when listing my history', async function () {
111 video3WatchedDate = new Date()
112 await server.views.view({ id: video3UUID, token: server.accessToken, currentTime: 2 })
113
114 const body = await server.history.list()
115
116 expect(body.total).to.equal(3)
117
118 const videos = body.data
119 expect(videos[0].name).to.equal('video 3')
120 expect(videos[1].name).to.equal('video 1')
121 expect(videos[2].name).to.equal('video 2')
122 })
123
124 it('Should not have videos history on another user', async function () {
125 const body = await server.history.list({ token: userAccessToken })
126
127 expect(body.total).to.equal(0)
128 expect(body.data).to.have.lengthOf(0)
129 })
130
131 it('Should be able to search through videos in my history', async function () {
132 const body = await server.history.list({ search: '2' })
133 expect(body.total).to.equal(1)
134
135 const videos = body.data
136 expect(videos[0].name).to.equal('video 2')
137 })
138
139 it('Should clear my history', async function () {
140 await server.history.removeAll({ beforeDate: video3WatchedDate.toISOString() })
141 })
142
143 it('Should have my history cleared', async function () {
144 const body = await server.history.list()
145 expect(body.total).to.equal(1)
146
147 const videos = body.data
148 expect(videos[0].name).to.equal('video 3')
149 })
150
151 it('Should disable videos history', async function () {
152 await server.users.updateMe({
153 videosHistoryEnabled: false
154 })
155
156 await server.views.view({ id: video2UUID, token: server.accessToken, currentTime: 8 })
157
158 const { data } = await server.history.list()
159 expect(data[0].name).to.not.equal('video 2')
160 })
161
162 it('Should re-enable videos history', async function () {
163 await server.users.updateMe({
164 videosHistoryEnabled: true
165 })
166
167 await server.views.view({ id: video2UUID, token: server.accessToken, currentTime: 8 })
168
169 const { data } = await server.history.list()
170 expect(data[0].name).to.equal('video 2')
171 })
172
173 it('Should not clean old history', async function () {
174 this.timeout(50000)
175
176 await killallServers([ server ])
177
178 await server.run({ history: { videos: { max_age: '10 days' } } })
179
180 await wait(6000)
181
182 // Should still have history
183
184 const body = await server.history.list()
185 expect(body.total).to.equal(2)
186 })
187
188 it('Should clean old history', async function () {
189 this.timeout(50000)
190
191 await killallServers([ server ])
192
193 await server.run({ history: { videos: { max_age: '5 seconds' } } })
194
195 await wait(6000)
196
197 const body = await server.history.list()
198 expect(body.total).to.equal(0)
199 })
200
201 it('Should delete a specific history element', async function () {
202 {
203 await server.views.view({ id: video1UUID, token: server.accessToken, currentTime: 4 })
204 await server.views.view({ id: video2UUID, token: server.accessToken, currentTime: 8 })
205 }
206
207 {
208 const body = await server.history.list()
209 expect(body.total).to.equal(2)
210 }
211
212 {
213 await server.history.removeElement({ videoId: video1Id })
214
215 const body = await server.history.list()
216 expect(body.total).to.equal(1)
217 expect(body.data[0].uuid).to.equal(video2UUID)
218 }
219 })
220
221 after(async function () {
222 await cleanupTests([ server ])
223 })
224})
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts
deleted file mode 100644
index f2496e35e..000000000
--- a/server/tests/api/videos/videos-overview.ts
+++ /dev/null
@@ -1,129 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { VideosOverview } from '@shared/models'
6import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7
8describe('Test a videos overview', function () {
9 let server: PeerTubeServer = null
10
11 function testOverviewCount (overview: VideosOverview, expected: number) {
12 expect(overview.tags).to.have.lengthOf(expected)
13 expect(overview.categories).to.have.lengthOf(expected)
14 expect(overview.channels).to.have.lengthOf(expected)
15 }
16
17 before(async function () {
18 this.timeout(30000)
19
20 server = await createSingleServer(1)
21
22 await setAccessTokensToServers([ server ])
23 })
24
25 it('Should send empty overview', async function () {
26 const body = await server.overviews.getVideos({ page: 1 })
27
28 testOverviewCount(body, 0)
29 })
30
31 it('Should upload 5 videos in a specific category, tag and channel but not include them in overview', async function () {
32 this.timeout(60000)
33
34 await wait(3000)
35
36 await server.videos.upload({
37 attributes: {
38 name: 'video 0',
39 category: 3,
40 tags: [ 'coucou1', 'coucou2' ]
41 }
42 })
43
44 const body = await server.overviews.getVideos({ page: 1 })
45
46 testOverviewCount(body, 0)
47 })
48
49 it('Should upload another video and include all videos in the overview', async function () {
50 this.timeout(120000)
51
52 {
53 for (let i = 1; i < 6; i++) {
54 await server.videos.upload({
55 attributes: {
56 name: 'video ' + i,
57 category: 3,
58 tags: [ 'coucou1', 'coucou2' ]
59 }
60 })
61 }
62
63 await wait(3000)
64 }
65
66 {
67 const body = await server.overviews.getVideos({ page: 1 })
68
69 testOverviewCount(body, 1)
70 }
71
72 {
73 const overview = await server.overviews.getVideos({ page: 2 })
74
75 expect(overview.tags).to.have.lengthOf(1)
76 expect(overview.categories).to.have.lengthOf(0)
77 expect(overview.channels).to.have.lengthOf(0)
78 }
79 })
80
81 it('Should have the correct overview', async function () {
82 const overview1 = await server.overviews.getVideos({ page: 1 })
83 const overview2 = await server.overviews.getVideos({ page: 2 })
84
85 for (const arr of [ overview1.tags, overview1.categories, overview1.channels, overview2.tags ]) {
86 expect(arr).to.have.lengthOf(1)
87
88 const obj = arr[0]
89
90 expect(obj.videos).to.have.lengthOf(6)
91 expect(obj.videos[0].name).to.equal('video 5')
92 expect(obj.videos[1].name).to.equal('video 4')
93 expect(obj.videos[2].name).to.equal('video 3')
94 expect(obj.videos[3].name).to.equal('video 2')
95 expect(obj.videos[4].name).to.equal('video 1')
96 expect(obj.videos[5].name).to.equal('video 0')
97 }
98
99 const tags = [ overview1.tags[0].tag, overview2.tags[0].tag ]
100 expect(tags.find(t => t === 'coucou1')).to.not.be.undefined
101 expect(tags.find(t => t === 'coucou2')).to.not.be.undefined
102
103 expect(overview1.categories[0].category.id).to.equal(3)
104
105 expect(overview1.channels[0].channel.name).to.equal('root_channel')
106 })
107
108 it('Should hide muted accounts', async function () {
109 const token = await server.users.generateUserAndToken('choco')
110
111 await server.blocklist.addToMyBlocklist({ token, account: 'root@' + server.host })
112
113 {
114 const body = await server.overviews.getVideos({ page: 1 })
115
116 testOverviewCount(body, 1)
117 }
118
119 {
120 const body = await server.overviews.getVideos({ page: 1, token })
121
122 testOverviewCount(body, 0)
123 }
124 })
125
126 after(async function () {
127 await cleanupTests([ server ])
128 })
129})