]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/server/follows.ts
Merge branch 'release/3.2.0' into develop
[github/Chocobozzz/PeerTube.git] / server / tests / api / server / follows.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import 'mocha'
4 import * as chai from 'chai'
5 import {
6 addVideoCommentReply,
7 addVideoCommentThread,
8 cleanupTests,
9 completeVideoCheck,
10 createUser,
11 createVideoCaption,
12 dateIsValid,
13 deleteVideoComment,
14 expectAccountFollows,
15 flushAndRunMultipleServers,
16 follow,
17 getFollowersListPaginationAndSort,
18 getFollowingListPaginationAndSort,
19 getVideoCommentThreads,
20 getVideosList,
21 getVideoThreadComments,
22 listVideoCaptions,
23 rateVideo,
24 ServerInfo,
25 setAccessTokensToServers,
26 testCaptionFile,
27 unfollow,
28 uploadVideo,
29 userLogin,
30 waitJobs
31 } from '@shared/extra-utils'
32 import { Video, VideoCaption, VideoComment, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
33
34 const expect = chai.expect
35
36 describe('Test follows', function () {
37 let servers: ServerInfo[] = []
38
39 before(async function () {
40 this.timeout(30000)
41
42 servers = await flushAndRunMultipleServers(3)
43
44 // Get the access tokens
45 await setAccessTokensToServers(servers)
46 })
47
48 it('Should not have followers', async function () {
49 for (const server of servers) {
50 const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
51 const follows = res.body.data
52
53 expect(res.body.total).to.equal(0)
54 expect(follows).to.be.an('array')
55 expect(follows.length).to.equal(0)
56 }
57 })
58
59 it('Should not have following', async function () {
60 for (const server of servers) {
61 const res = await getFollowingListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
62 const follows = res.body.data
63
64 expect(res.body.total).to.equal(0)
65 expect(follows).to.be.an('array')
66 expect(follows.length).to.equal(0)
67 }
68 })
69
70 it('Should have server 1 following server 2 and 3', async function () {
71 this.timeout(30000)
72
73 await follow(servers[0].url, [ servers[1].url, servers[2].url ], servers[0].accessToken)
74
75 await waitJobs(servers)
76 })
77
78 it('Should have 2 followings on server 1', async function () {
79 let res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 1, sort: 'createdAt' })
80 let follows = res.body.data
81
82 expect(res.body.total).to.equal(2)
83 expect(follows).to.be.an('array')
84 expect(follows.length).to.equal(1)
85
86 res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 1, count: 1, sort: 'createdAt' })
87 follows = follows.concat(res.body.data)
88
89 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
90 const server3Follow = follows.find(f => f.following.host === 'localhost:' + servers[2].port)
91
92 expect(server2Follow).to.not.be.undefined
93 expect(server3Follow).to.not.be.undefined
94 expect(server2Follow.state).to.equal('accepted')
95 expect(server3Follow.state).to.equal('accepted')
96 })
97
98 it('Should search/filter followings on server 1', async function () {
99 const sort = 'createdAt'
100 const start = 0
101 const count = 1
102 const url = servers[0].url
103
104 {
105 const search = ':' + servers[1].port
106
107 {
108 const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search })
109 const follows = res.body.data
110
111 expect(res.body.total).to.equal(1)
112 expect(follows.length).to.equal(1)
113 expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
114 }
115
116 {
117 const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'accepted' })
118 expect(res.body.total).to.equal(1)
119 expect(res.body.data).to.have.lengthOf(1)
120 }
121
122 {
123 const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'accepted', actorType: 'Person' })
124 expect(res.body.total).to.equal(0)
125 expect(res.body.data).to.have.lengthOf(0)
126 }
127
128 {
129 const res = await getFollowingListPaginationAndSort({
130 url,
131 start,
132 count,
133 sort,
134 search,
135 state: 'accepted',
136 actorType: 'Application'
137 })
138 expect(res.body.total).to.equal(1)
139 expect(res.body.data).to.have.lengthOf(1)
140 }
141
142 {
143 const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search, state: 'pending' })
144 expect(res.body.total).to.equal(0)
145 expect(res.body.data).to.have.lengthOf(0)
146 }
147 }
148
149 {
150 const res = await getFollowingListPaginationAndSort({ url, start, count, sort, search: 'bla' })
151 const follows = res.body.data
152
153 expect(res.body.total).to.equal(0)
154 expect(follows.length).to.equal(0)
155 }
156 })
157
158 it('Should have 0 followings on server 2 and 3', async function () {
159 for (const server of [ servers[1], servers[2] ]) {
160 const res = await getFollowingListPaginationAndSort({ url: server.url, start: 0, count: 5, sort: 'createdAt' })
161 const follows = res.body.data
162
163 expect(res.body.total).to.equal(0)
164 expect(follows).to.be.an('array')
165 expect(follows.length).to.equal(0)
166 }
167 })
168
169 it('Should have 1 followers on server 2 and 3', async function () {
170 for (const server of [ servers[1], servers[2] ]) {
171 const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' })
172
173 const follows = res.body.data
174 expect(res.body.total).to.equal(1)
175 expect(follows).to.be.an('array')
176 expect(follows.length).to.equal(1)
177 expect(follows[0].follower.host).to.equal('localhost:' + servers[0].port)
178 }
179 })
180
181 it('Should search/filter followers on server 2', async function () {
182 const url = servers[2].url
183 const start = 0
184 const count = 5
185 const sort = 'createdAt'
186
187 {
188 const search = servers[0].port + ''
189
190 {
191 const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search })
192 const follows = res.body.data
193
194 expect(res.body.total).to.equal(1)
195 expect(follows.length).to.equal(1)
196 expect(follows[0].following.host).to.equal('localhost:' + servers[2].port)
197 }
198
199 {
200 const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'accepted' })
201 expect(res.body.total).to.equal(1)
202 expect(res.body.data).to.have.lengthOf(1)
203 }
204
205 {
206 const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'accepted', actorType: 'Person' })
207 expect(res.body.total).to.equal(0)
208 expect(res.body.data).to.have.lengthOf(0)
209 }
210
211 {
212 const res = await getFollowersListPaginationAndSort({
213 url,
214 start,
215 count,
216 sort,
217 search,
218 state: 'accepted',
219 actorType: 'Application'
220 })
221 expect(res.body.total).to.equal(1)
222 expect(res.body.data).to.have.lengthOf(1)
223 }
224
225 {
226 const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search, state: 'pending' })
227 expect(res.body.total).to.equal(0)
228 expect(res.body.data).to.have.lengthOf(0)
229 }
230 }
231
232 {
233 const res = await getFollowersListPaginationAndSort({ url, start, count, sort, search: 'bla' })
234 const follows = res.body.data
235
236 expect(res.body.total).to.equal(0)
237 expect(follows.length).to.equal(0)
238 }
239 })
240
241 it('Should have 0 followers on server 1', async function () {
242 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
243 const follows = res.body.data
244
245 expect(res.body.total).to.equal(0)
246 expect(follows).to.be.an('array')
247 expect(follows.length).to.equal(0)
248 })
249
250 it('Should have the correct follows counts', async function () {
251 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
252 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
253 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
254
255 // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
256 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
257 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
258
259 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 1)
260 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
261 })
262
263 it('Should unfollow server 3 on server 1', async function () {
264 this.timeout(5000)
265
266 await unfollow(servers[0].url, servers[0].accessToken, servers[2])
267
268 await waitJobs(servers)
269 })
270
271 it('Should not follow server 3 on server 1 anymore', async function () {
272 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
273 const follows = res.body.data
274
275 expect(res.body.total).to.equal(1)
276 expect(follows).to.be.an('array')
277 expect(follows.length).to.equal(1)
278
279 expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
280 })
281
282 it('Should not have server 1 as follower on server 3 anymore', async function () {
283 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 1, sort: 'createdAt' })
284
285 const follows = res.body.data
286 expect(res.body.total).to.equal(0)
287 expect(follows).to.be.an('array')
288 expect(follows.length).to.equal(0)
289 })
290
291 it('Should have the correct follows counts 2', async function () {
292 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 1)
293 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
294
295 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
296 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
297
298 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 0)
299 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 0, 0)
300 })
301
302 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
303 this.timeout(60000)
304
305 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'server2' })
306 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3' })
307
308 await waitJobs(servers)
309
310 let res = await getVideosList(servers[0].url)
311 expect(res.body.total).to.equal(1)
312 expect(res.body.data[0].name).to.equal('server2')
313
314 res = await getVideosList(servers[1].url)
315 expect(res.body.total).to.equal(1)
316 expect(res.body.data[0].name).to.equal('server2')
317
318 res = await getVideosList(servers[2].url)
319 expect(res.body.total).to.equal(1)
320 expect(res.body.data[0].name).to.equal('server3')
321 })
322
323 describe('Should propagate data on a new following', function () {
324 let video4: Video
325
326 before(async function () {
327 this.timeout(50000)
328
329 const video4Attributes = {
330 name: 'server3-4',
331 category: 2,
332 nsfw: true,
333 licence: 6,
334 tags: [ 'tag1', 'tag2', 'tag3' ]
335 }
336
337 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-2' })
338 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-3' })
339 await uploadVideo(servers[2].url, servers[2].accessToken, video4Attributes)
340 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-5' })
341 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-6' })
342
343 {
344 const user = { username: 'captain', password: 'password' }
345 await createUser({ url: servers[2].url, accessToken: servers[2].accessToken, username: user.username, password: user.password })
346 const userAccessToken = await userLogin(servers[2], user)
347
348 const resVideos = await getVideosList(servers[2].url)
349 video4 = resVideos.body.data.find(v => v.name === 'server3-4')
350
351 {
352 await rateVideo(servers[2].url, servers[2].accessToken, video4.id, 'like')
353 await rateVideo(servers[2].url, userAccessToken, video4.id, 'dislike')
354 }
355
356 {
357 {
358 const text = 'my super first comment'
359 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
360 const threadId = res.body.comment.id
361
362 const text1 = 'my super answer to thread 1'
363 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
364 const childCommentId = childCommentRes.body.comment.id
365
366 const text2 = 'my super answer to answer of thread 1'
367 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text2)
368
369 const text3 = 'my second answer to thread 1'
370 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text3)
371 }
372
373 {
374 const text = 'will be deleted'
375 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
376 const threadId = res.body.comment.id
377
378 const text1 = 'answer to deleted'
379 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
380
381 const text2 = 'will also be deleted'
382 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text2)
383 const childCommentId = childCommentRes.body.comment.id
384
385 const text3 = 'my second answer to deleted'
386 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text3)
387
388 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, threadId)
389 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, childCommentId)
390 }
391 }
392
393 {
394 await createVideoCaption({
395 url: servers[2].url,
396 accessToken: servers[2].accessToken,
397 language: 'ar',
398 videoId: video4.id,
399 fixture: 'subtitle-good2.vtt'
400 })
401 }
402 }
403
404 await waitJobs(servers)
405
406 // Server 1 follows server 3
407 await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken)
408
409 await waitJobs(servers)
410 })
411
412 it('Should have the correct follows counts 3', async function () {
413 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
414 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
415 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
416
417 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
418 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
419
420 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 1)
421 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
422 })
423
424 it('Should have propagated videos', async function () {
425 const res = await getVideosList(servers[0].url)
426 expect(res.body.total).to.equal(7)
427
428 const video2 = res.body.data.find(v => v.name === 'server3-2')
429 video4 = res.body.data.find(v => v.name === 'server3-4')
430 const video6 = res.body.data.find(v => v.name === 'server3-6')
431
432 expect(video2).to.not.be.undefined
433 expect(video4).to.not.be.undefined
434 expect(video6).to.not.be.undefined
435
436 const isLocal = false
437 const checkAttributes = {
438 name: 'server3-4',
439 category: 2,
440 licence: 6,
441 language: 'zh',
442 nsfw: true,
443 description: 'my super description',
444 support: 'my super support text',
445 account: {
446 name: 'root',
447 host: 'localhost:' + servers[2].port
448 },
449 isLocal,
450 commentsEnabled: true,
451 downloadEnabled: true,
452 duration: 5,
453 tags: [ 'tag1', 'tag2', 'tag3' ],
454 privacy: VideoPrivacy.PUBLIC,
455 likes: 1,
456 dislikes: 1,
457 channel: {
458 displayName: 'Main root channel',
459 name: 'root_channel',
460 description: '',
461 isLocal
462 },
463 fixture: 'video_short.webm',
464 files: [
465 {
466 resolution: 720,
467 size: 218910
468 }
469 ]
470 }
471 await completeVideoCheck(servers[0].url, video4, checkAttributes)
472 })
473
474 it('Should have propagated comments', async function () {
475 const res1 = await getVideoCommentThreads(servers[0].url, video4.id, 0, 5, 'createdAt')
476
477 expect(res1.body.total).to.equal(2)
478 expect(res1.body.data).to.be.an('array')
479 expect(res1.body.data).to.have.lengthOf(2)
480
481 {
482 const comment: VideoComment = res1.body.data[0]
483 expect(comment.inReplyToCommentId).to.be.null
484 expect(comment.text).equal('my super first comment')
485 expect(comment.videoId).to.equal(video4.id)
486 expect(comment.id).to.equal(comment.threadId)
487 expect(comment.account.name).to.equal('root')
488 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
489 expect(comment.totalReplies).to.equal(3)
490 expect(dateIsValid(comment.createdAt as string)).to.be.true
491 expect(dateIsValid(comment.updatedAt as string)).to.be.true
492
493 const threadId = comment.threadId
494
495 const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId)
496
497 const tree: VideoCommentThreadTree = res2.body
498 expect(tree.comment.text).equal('my super first comment')
499 expect(tree.children).to.have.lengthOf(2)
500
501 const firstChild = tree.children[0]
502 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
503 expect(firstChild.children).to.have.lengthOf(1)
504
505 const childOfFirstChild = firstChild.children[0]
506 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
507 expect(childOfFirstChild.children).to.have.lengthOf(0)
508
509 const secondChild = tree.children[1]
510 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
511 expect(secondChild.children).to.have.lengthOf(0)
512 }
513
514 {
515 const deletedComment: VideoComment = res1.body.data[1]
516 expect(deletedComment).to.not.be.undefined
517 expect(deletedComment.isDeleted).to.be.true
518 expect(deletedComment.deletedAt).to.not.be.null
519 expect(deletedComment.text).to.equal('')
520 expect(deletedComment.inReplyToCommentId).to.be.null
521 expect(deletedComment.account).to.be.null
522 expect(deletedComment.totalReplies).to.equal(3)
523 expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true
524
525 const res2 = await getVideoThreadComments(servers[0].url, video4.id, deletedComment.threadId)
526
527 const tree: VideoCommentThreadTree = res2.body
528 const [ commentRoot, deletedChildRoot ] = tree.children
529
530 expect(deletedChildRoot).to.not.be.undefined
531 expect(deletedChildRoot.comment.isDeleted).to.be.true
532 expect(deletedChildRoot.comment.deletedAt).to.not.be.null
533 expect(deletedChildRoot.comment.text).to.equal('')
534 expect(deletedChildRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
535 expect(deletedChildRoot.comment.account).to.be.null
536 expect(deletedChildRoot.children).to.have.lengthOf(1)
537
538 const answerToDeletedChild = deletedChildRoot.children[0]
539 expect(answerToDeletedChild.comment).to.not.be.undefined
540 expect(answerToDeletedChild.comment.inReplyToCommentId).to.equal(deletedChildRoot.comment.id)
541 expect(answerToDeletedChild.comment.text).to.equal('my second answer to deleted')
542 expect(answerToDeletedChild.comment.account.name).to.equal('root')
543
544 expect(commentRoot.comment).to.not.be.undefined
545 expect(commentRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
546 expect(commentRoot.comment.text).to.equal('answer to deleted')
547 expect(commentRoot.comment.account.name).to.equal('root')
548 }
549 })
550
551 it('Should have propagated captions', async function () {
552 const res = await listVideoCaptions(servers[0].url, video4.id)
553 expect(res.body.total).to.equal(1)
554 expect(res.body.data).to.have.lengthOf(1)
555
556 const caption1: VideoCaption = res.body.data[0]
557 expect(caption1.language.id).to.equal('ar')
558 expect(caption1.language.label).to.equal('Arabic')
559 expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/.+-ar.vtt$'))
560 await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
561 })
562
563 it('Should unfollow server 3 on server 1 and does not list server 3 videos', async function () {
564 this.timeout(5000)
565
566 await unfollow(servers[0].url, servers[0].accessToken, servers[2])
567
568 await waitJobs(servers)
569
570 const res = await getVideosList(servers[0].url)
571 expect(res.body.total).to.equal(1)
572 })
573
574 })
575
576 after(async function () {
577 await cleanupTests(servers)
578 })
579 })