diff options
Diffstat (limited to 'server/tests/api/videos')
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 | |||
3 | import { expect } from 'chai' | ||
4 | import { FIXTURE_URLS } from '@server/tests/shared' | ||
5 | import { areHttpImportTestsDisabled } from '@shared/core-utils' | ||
6 | import { | ||
7 | createSingleServer, | ||
8 | getServerImportConfig, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers, | ||
11 | setDefaultVideoChannel, | ||
12 | waitJobs | ||
13 | } from '@shared/server-commands' | ||
14 | |||
15 | describe('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 @@ | |||
1 | import './multiple-servers' | ||
2 | import './resumable-upload' | ||
3 | import './single-server' | ||
4 | import './video-captions' | ||
5 | import './video-change-ownership' | ||
6 | import './video-channels' | ||
7 | import './channel-import-videos' | ||
8 | import './video-channel-syncs' | ||
9 | import './video-comments' | ||
10 | import './video-description' | ||
11 | import './video-files' | ||
12 | import './video-imports' | ||
13 | import './video-nsfw' | ||
14 | import './video-playlists' | ||
15 | import './video-playlist-thumbnails' | ||
16 | import './video-source' | ||
17 | import './video-privacy' | ||
18 | import './video-schedule-update' | ||
19 | import './videos-common-filters' | ||
20 | import './videos-history' | ||
21 | import './videos-overview' | ||
22 | import './video-static-file-privacy' | ||
23 | import './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 | |||
3 | import { expect } from 'chai' | ||
4 | import request from 'supertest' | ||
5 | import { | ||
6 | checkTmpIsEmpty, | ||
7 | checkVideoFilesWereRemoved, | ||
8 | checkWebTorrentWorks, | ||
9 | completeVideoCheck, | ||
10 | dateIsValid, | ||
11 | saveVideoInServers, | ||
12 | testImageGeneratedByFFmpeg | ||
13 | } from '@server/tests/shared' | ||
14 | import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' | ||
15 | import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models' | ||
16 | import { | ||
17 | cleanupTests, | ||
18 | createMultipleServers, | ||
19 | doubleFollow, | ||
20 | makeGetRequest, | ||
21 | PeerTubeServer, | ||
22 | setAccessTokensToServers, | ||
23 | setDefaultAccountAvatar, | ||
24 | setDefaultChannelAvatar, | ||
25 | waitJobs | ||
26 | } from '@shared/server-commands' | ||
27 | |||
28 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { pathExists, readdir, stat } from 'fs-extra' | ||
5 | import { join } from 'path' | ||
6 | import { buildAbsoluteFixturePath } from '@shared/core-utils' | ||
7 | import { sha1 } from '@shared/extra-utils' | ||
8 | import { HttpStatusCode, VideoPrivacy } from '@shared/models' | ||
9 | import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands' | ||
10 | |||
11 | // Most classic resumable upload tests are done in other test suites | ||
12 | |||
13 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkVideoFilesWereRemoved, completeVideoCheck, testImageGeneratedByFFmpeg } from '@server/tests/shared' | ||
5 | import { wait } from '@shared/core-utils' | ||
6 | import { Video, VideoPrivacy } from '@shared/models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createSingleServer, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultAccountAvatar, | ||
13 | setDefaultChannelAvatar, | ||
14 | waitJobs | ||
15 | } from '@shared/server-commands' | ||
16 | |||
17 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkVideoFilesWereRemoved, testCaptionFile } from '@server/tests/shared' | ||
5 | import { wait } from '@shared/core-utils' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | waitJobs | ||
13 | } from '@shared/server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | ChangeOwnershipCommand, | ||
6 | cleanupTests, | ||
7 | createMultipleServers, | ||
8 | createSingleServer, | ||
9 | doubleFollow, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | waitJobs | ||
14 | } from '@shared/server-commands' | ||
15 | import { HttpStatusCode, VideoPrivacy } from '@shared/models' | ||
16 | |||
17 | describe('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 | |||
237 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { FIXTURE_URLS, SQLCommand } from '@server/tests/shared' | ||
5 | import { areHttpImportTestsDisabled } from '@shared/core-utils' | ||
6 | import { VideoChannelSyncState, VideoInclude, VideoPrivacy } from '@shared/models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createMultipleServers, | ||
10 | getServerImportConfig, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers, | ||
13 | setDefaultAccountAvatar, | ||
14 | setDefaultChannelAvatar, | ||
15 | setDefaultVideoChannel, | ||
16 | waitJobs | ||
17 | } from '@shared/server-commands' | ||
18 | |||
19 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { basename } from 'path' | ||
5 | import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' | ||
6 | import { SQLCommand, testFileExistsOrNot, testImage } from '@server/tests/shared' | ||
7 | import { wait } from '@shared/core-utils' | ||
8 | import { ActorImageType, User, VideoChannel } from '@shared/models' | ||
9 | import { | ||
10 | cleanupTests, | ||
11 | createMultipleServers, | ||
12 | doubleFollow, | ||
13 | PeerTubeServer, | ||
14 | setAccessTokensToServers, | ||
15 | setDefaultAccountAvatar, | ||
16 | setDefaultVideoChannel, | ||
17 | waitJobs | ||
18 | } from '@shared/server-commands' | ||
19 | |||
20 | async 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 | |||
26 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { dateIsValid, testImage } from '@server/tests/shared' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | CommentsCommand, | ||
8 | createSingleServer, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers, | ||
11 | setDefaultAccountAvatar, | ||
12 | setDefaultChannelAvatar | ||
13 | } from '@shared/server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createMultipleServers, | ||
7 | doubleFollow, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | waitJobs | ||
11 | } from '@shared/server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { HttpStatusCode } from '@shared/models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createMultipleServers, | ||
8 | doubleFollow, | ||
9 | makeRawRequest, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | waitJobs | ||
13 | } from '@shared/server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { pathExists, readdir, remove } from 'fs-extra' | ||
5 | import { join } from 'path' | ||
6 | import { FIXTURE_URLS, testCaptionFile, testImageGeneratedByFFmpeg } from '@server/tests/shared' | ||
7 | import { areHttpImportTestsDisabled } from '@shared/core-utils' | ||
8 | import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models' | ||
9 | import { | ||
10 | cleanupTests, | ||
11 | createMultipleServers, | ||
12 | createSingleServer, | ||
13 | doubleFollow, | ||
14 | getServerImportConfig, | ||
15 | PeerTubeServer, | ||
16 | setAccessTokensToServers, | ||
17 | setDefaultVideoChannel, | ||
18 | waitJobs | ||
19 | } from '@shared/server-commands' | ||
20 | import { DeepPartial } from '@shared/typescript-utils' | ||
21 | |||
22 | async 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 | |||
59 | async 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 | |||
78 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' | ||
5 | import { BooleanBothQuery, CustomConfig, ResultList, Video, VideosOverview } from '@shared/models' | ||
6 | |||
7 | function createOverviewRes (overview: VideosOverview) { | ||
8 | const videos = overview.categories[0].videos | ||
9 | return { data: videos, total: videos.length } | ||
10 | } | ||
11 | |||
12 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | VideoPasswordsCommand, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | setDefaultAccountAvatar, | ||
11 | setDefaultChannelAvatar | ||
12 | } from '@shared/server-commands' | ||
13 | import { VideoPrivacy } from '@shared/models' | ||
14 | |||
15 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { testImageGeneratedByFFmpeg } from '@server/tests/shared' | ||
5 | import { VideoPlaylistPrivacy } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | waitJobs | ||
14 | } from '@shared/server-commands' | ||
15 | |||
16 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkPlaylistFilesWereRemoved, testImageGeneratedByFFmpeg } from '@server/tests/shared' | ||
5 | import { wait } from '@shared/core-utils' | ||
6 | import { uuidToShort } from '@shared/extra-utils' | ||
7 | import { | ||
8 | HttpStatusCode, | ||
9 | VideoPlaylist, | ||
10 | VideoPlaylistCreateResult, | ||
11 | VideoPlaylistElementType, | ||
12 | VideoPlaylistPrivacy, | ||
13 | VideoPlaylistType, | ||
14 | VideoPrivacy | ||
15 | } from '@shared/models' | ||
16 | import { | ||
17 | cleanupTests, | ||
18 | createMultipleServers, | ||
19 | doubleFollow, | ||
20 | PeerTubeServer, | ||
21 | PlaylistsCommand, | ||
22 | setAccessTokensToServers, | ||
23 | setDefaultAccountAvatar, | ||
24 | setDefaultVideoChannel, | ||
25 | waitJobs | ||
26 | } from '@shared/server-commands' | ||
27 | |||
28 | async 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 | |||
52 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models' | ||
6 | import { cleanupTests, createSingleServer, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands' | ||
7 | |||
8 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | waitJobs | ||
13 | } from '@shared/server-commands' | ||
14 | |||
15 | function in10Seconds () { | ||
16 | const now = new Date() | ||
17 | now.setSeconds(now.getSeconds() + 10) | ||
18 | |||
19 | return now | ||
20 | } | ||
21 | |||
22 | describe('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 @@ | |||
1 | import { expect } from 'chai' | ||
2 | import { expectStartWith } from '@server/tests/shared' | ||
3 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
4 | import { areMockObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils' | ||
5 | import { HttpStatusCode } from '@shared/models' | ||
6 | import { | ||
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 | |||
20 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { decode } from 'magnet-uri' | ||
5 | import { checkVideoFileTokenReinjection, expectStartWith, parseTorrentVideo } from '@server/tests/shared' | ||
6 | import { getAllFiles, wait } from '@shared/core-utils' | ||
7 | import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models' | ||
8 | import { | ||
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 | |||
21 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { readdir } from 'fs-extra' | ||
5 | import { basename } from 'path' | ||
6 | import { FIXTURE_URLS } from '@server/tests/shared' | ||
7 | import { areHttpImportTestsDisabled } from '@shared/core-utils' | ||
8 | import { HttpStatusCode, VideoPrivacy } from '@shared/models' | ||
9 | import { | ||
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 | |||
22 | async 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 | |||
50 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { pick } from '@shared/core-utils' | ||
5 | import { HttpStatusCode, UserRole, Video, VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | makeGetRequest, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers, | ||
13 | setDefaultAccountAvatar, | ||
14 | setDefaultVideoChannel, | ||
15 | waitJobs | ||
16 | } from '@shared/server-commands' | ||
17 | |||
18 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { Video } from '@shared/models' | ||
6 | import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' | ||
7 | |||
8 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { VideosOverview } from '@shared/models' | ||
6 | import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' | ||
7 | |||
8 | describe('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 | }) | ||