1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { completeVideoCheck, dateIsValid, expectAccountFollows, expectChannelsFollows, testCaptionFile } from '@server/tests/shared'
5 import { Video, VideoPrivacy } from '@shared/models'
6 import { cleanupTests, createMultipleServers, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
8 describe('Test follows', function () {
9 let servers: PeerTubeServer[] = []
11 before(async function () {
14 servers = await createMultipleServers(3)
16 // Get the access tokens
17 await setAccessTokensToServers(servers)
20 describe('Data propagation after follow', function () {
22 it('Should not have followers/followings', async function () {
23 for (const server of servers) {
24 const bodies = await Promise.all([
25 server.follows.getFollowers({ start: 0, count: 5, sort: 'createdAt' }),
26 server.follows.getFollowings({ start: 0, count: 5, sort: 'createdAt' })
29 for (const body of bodies) {
30 expect(body.total).to.equal(0)
32 const follows = body.data
33 expect(follows).to.be.an('array')
34 expect(follows).to.have.lengthOf(0)
39 it('Should have server 1 following root account of server 2 and server 3', async function () {
42 await servers[0].follows.follow({
43 hosts: [ servers[2].url ],
44 handles: [ 'root@' + servers[1].host ]
47 await waitJobs(servers)
50 it('Should have 2 followings on server 1', async function () {
51 const body = await servers[0].follows.getFollowings({ start: 0, count: 1, sort: 'createdAt' })
52 expect(body.total).to.equal(2)
54 let follows = body.data
55 expect(follows).to.be.an('array')
56 expect(follows).to.have.lengthOf(1)
58 const body2 = await servers[0].follows.getFollowings({ start: 1, count: 1, sort: 'createdAt' })
59 follows = follows.concat(body2.data)
61 const server2Follow = follows.find(f => f.following.host === servers[1].host)
62 const server3Follow = follows.find(f => f.following.host === servers[2].host)
64 expect(server2Follow).to.not.be.undefined
65 expect(server2Follow.following.name).to.equal('root')
66 expect(server2Follow.state).to.equal('accepted')
68 expect(server3Follow).to.not.be.undefined
69 expect(server3Follow.following.name).to.equal('peertube')
70 expect(server3Follow.state).to.equal('accepted')
73 it('Should have 0 followings on server 2 and 3', async function () {
74 for (const server of [ servers[1], servers[2] ]) {
75 const body = await server.follows.getFollowings({ start: 0, count: 5, sort: 'createdAt' })
76 expect(body.total).to.equal(0)
78 const follows = body.data
79 expect(follows).to.be.an('array')
80 expect(follows).to.have.lengthOf(0)
84 it('Should have 1 followers on server 3', async function () {
85 const body = await servers[2].follows.getFollowers({ start: 0, count: 1, sort: 'createdAt' })
86 expect(body.total).to.equal(1)
88 const follows = body.data
89 expect(follows).to.be.an('array')
90 expect(follows).to.have.lengthOf(1)
91 expect(follows[0].follower.host).to.equal(servers[0].host)
94 it('Should have 0 followers on server 1 and 2', async function () {
95 for (const server of [ servers[0], servers[1] ]) {
96 const body = await server.follows.getFollowers({ start: 0, count: 5, sort: 'createdAt' })
97 expect(body.total).to.equal(0)
99 const follows = body.data
100 expect(follows).to.be.an('array')
101 expect(follows).to.have.lengthOf(0)
105 it('Should search/filter followings on server 1', async function () {
106 const sort = 'createdAt'
111 const search = ':' + servers[1].port
114 const body = await servers[0].follows.getFollowings({ start, count, sort, search })
115 expect(body.total).to.equal(1)
117 const follows = body.data
118 expect(follows).to.have.lengthOf(1)
119 expect(follows[0].following.host).to.equal(servers[1].host)
123 const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'accepted' })
124 expect(body.total).to.equal(1)
125 expect(body.data).to.have.lengthOf(1)
129 const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'accepted', actorType: 'Person' })
130 expect(body.total).to.equal(1)
131 expect(body.data).to.have.lengthOf(1)
135 const body = await servers[0].follows.getFollowings({
141 actorType: 'Application'
143 expect(body.total).to.equal(0)
144 expect(body.data).to.have.lengthOf(0)
148 const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'pending' })
149 expect(body.total).to.equal(0)
150 expect(body.data).to.have.lengthOf(0)
155 const body = await servers[0].follows.getFollowings({ start, count, sort, search: 'root' })
156 expect(body.total).to.equal(1)
157 expect(body.data).to.have.lengthOf(1)
161 const body = await servers[0].follows.getFollowings({ start, count, sort, search: 'bla' })
162 expect(body.total).to.equal(0)
164 expect(body.data).to.have.lengthOf(0)
168 it('Should search/filter followers on server 2', async function () {
171 const sort = 'createdAt'
174 const search = servers[0].port + ''
177 const body = await servers[2].follows.getFollowers({ start, count, sort, search })
178 expect(body.total).to.equal(1)
180 const follows = body.data
181 expect(follows).to.have.lengthOf(1)
182 expect(follows[0].following.host).to.equal(servers[2].host)
186 const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'accepted' })
187 expect(body.total).to.equal(1)
188 expect(body.data).to.have.lengthOf(1)
192 const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'accepted', actorType: 'Person' })
193 expect(body.total).to.equal(0)
194 expect(body.data).to.have.lengthOf(0)
198 const body = await servers[2].follows.getFollowers({
204 actorType: 'Application'
206 expect(body.total).to.equal(1)
207 expect(body.data).to.have.lengthOf(1)
211 const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'pending' })
212 expect(body.total).to.equal(0)
213 expect(body.data).to.have.lengthOf(0)
218 const body = await servers[2].follows.getFollowers({ start, count, sort, search: 'bla' })
219 expect(body.total).to.equal(0)
221 const follows = body.data
222 expect(follows).to.have.lengthOf(0)
226 it('Should have the correct follows counts', async function () {
227 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 2 })
228 await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
229 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
231 // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
232 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
233 await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
234 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
236 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
237 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
240 it('Should unfollow server 3 on server 1', async function () {
243 await servers[0].follows.unfollow({ target: servers[2] })
245 await waitJobs(servers)
248 it('Should not follow server 3 on server 1 anymore', async function () {
249 const body = await servers[0].follows.getFollowings({ start: 0, count: 2, sort: 'createdAt' })
250 expect(body.total).to.equal(1)
252 const follows = body.data
253 expect(follows).to.be.an('array')
254 expect(follows).to.have.lengthOf(1)
256 expect(follows[0].following.host).to.equal(servers[1].host)
259 it('Should not have server 1 as follower on server 3 anymore', async function () {
260 const body = await servers[2].follows.getFollowers({ start: 0, count: 1, sort: 'createdAt' })
261 expect(body.total).to.equal(0)
263 const follows = body.data
264 expect(follows).to.be.an('array')
265 expect(follows).to.have.lengthOf(0)
268 it('Should have the correct follows counts after the unfollow', async function () {
269 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
270 await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
271 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 0, following: 0 })
273 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
274 await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
275 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
277 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 0 })
278 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 0, following: 0 })
281 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
284 await servers[1].videos.upload({ attributes: { name: 'server2' } })
285 await servers[2].videos.upload({ attributes: { name: 'server3' } })
287 await waitJobs(servers)
290 const { total, data } = await servers[0].videos.list()
291 expect(total).to.equal(1)
292 expect(data[0].name).to.equal('server2')
296 const { total, data } = await servers[1].videos.list()
297 expect(total).to.equal(1)
298 expect(data[0].name).to.equal('server2')
302 const { total, data } = await servers[2].videos.list()
303 expect(total).to.equal(1)
304 expect(data[0].name).to.equal('server3')
308 it('Should remove account follow', async function () {
311 await servers[0].follows.unfollow({ target: 'root@' + servers[1].host })
313 await waitJobs(servers)
316 it('Should have removed the account follow', async function () {
317 await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
318 await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
321 const { total, data } = await servers[0].follows.getFollowings()
322 expect(total).to.equal(0)
323 expect(data).to.have.lengthOf(0)
327 const { total, data } = await servers[0].videos.list()
328 expect(total).to.equal(0)
329 expect(data).to.have.lengthOf(0)
333 it('Should follow a channel', async function () {
336 await servers[0].follows.follow({
337 handles: [ 'root_channel@' + servers[1].host ]
340 await waitJobs(servers)
342 await expectChannelsFollows({ server: servers[0], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
343 await expectChannelsFollows({ server: servers[1], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
346 const { total, data } = await servers[0].follows.getFollowings()
347 expect(total).to.equal(1)
348 expect(data).to.have.lengthOf(1)
352 const { total, data } = await servers[0].videos.list()
353 expect(total).to.equal(1)
354 expect(data).to.have.lengthOf(1)
359 describe('Should propagate data on a new server follow', function () {
362 before(async function () {
365 const video4Attributes = {
370 tags: [ 'tag1', 'tag2', 'tag3' ]
373 await servers[2].videos.upload({ attributes: { name: 'server3-2' } })
374 await servers[2].videos.upload({ attributes: { name: 'server3-3' } })
375 const video4CreateResult = await servers[2].videos.upload({ attributes: video4Attributes })
376 await servers[2].videos.upload({ attributes: { name: 'server3-5' } })
377 await servers[2].videos.upload({ attributes: { name: 'server3-6' } })
380 const userAccessToken = await servers[2].users.generateUserAndToken('captain')
382 await servers[2].videos.rate({ id: video4CreateResult.id, rating: 'like' })
383 await servers[2].videos.rate({ token: userAccessToken, id: video4CreateResult.id, rating: 'dislike' })
387 await servers[2].comments.createThread({ videoId: video4CreateResult.id, text: 'my super first comment' })
389 await servers[2].comments.addReplyToLastThread({ text: 'my super answer to thread 1' })
390 await servers[2].comments.addReplyToLastReply({ text: 'my super answer to answer of thread 1' })
391 await servers[2].comments.addReplyToLastThread({ text: 'my second answer to thread 1' })
395 const { id: threadId } = await servers[2].comments.createThread({ videoId: video4CreateResult.id, text: 'will be deleted' })
396 await servers[2].comments.addReplyToLastThread({ text: 'answer to deleted' })
398 const { id: replyId } = await servers[2].comments.addReplyToLastThread({ text: 'will also be deleted' })
400 await servers[2].comments.addReplyToLastReply({ text: 'my second answer to deleted' })
402 await servers[2].comments.delete({ videoId: video4CreateResult.id, commentId: threadId })
403 await servers[2].comments.delete({ videoId: video4CreateResult.id, commentId: replyId })
406 await servers[2].captions.add({
408 videoId: video4CreateResult.id,
409 fixture: 'subtitle-good2.vtt'
412 await waitJobs(servers)
414 // Server 1 follows server 3
415 await servers[0].follows.follow({ hosts: [ servers[2].url ] })
417 await waitJobs(servers)
420 it('Should have the correct follows counts', async function () {
421 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 2 })
422 await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
423 await expectChannelsFollows({ server: servers[0], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
424 await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
426 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
427 await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
428 await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
429 await expectChannelsFollows({ server: servers[1], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
431 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
432 await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
435 it('Should have propagated videos', async function () {
436 const { total, data } = await servers[0].videos.list()
437 expect(total).to.equal(7)
439 const video2 = data.find(v => v.name === 'server3-2')
440 video4 = data.find(v => v.name === 'server3-4')
441 const video6 = data.find(v => v.name === 'server3-6')
443 expect(video2).to.not.be.undefined
444 expect(video4).to.not.be.undefined
445 expect(video6).to.not.be.undefined
447 const isLocal = false
448 const checkAttributes = {
454 description: 'my super description',
455 support: 'my super support text',
458 host: servers[2].host
461 commentsEnabled: true,
462 downloadEnabled: true,
464 tags: [ 'tag1', 'tag2', 'tag3' ],
465 privacy: VideoPrivacy.PUBLIC,
469 displayName: 'Main root channel',
470 name: 'root_channel',
474 fixture: 'video_short.webm',
482 await completeVideoCheck({
484 originServer: servers[2],
485 videoUUID: video4.uuid,
486 attributes: checkAttributes
490 it('Should have propagated comments', async function () {
491 const { total, data } = await servers[0].comments.listThreads({ videoId: video4.id, sort: 'createdAt' })
493 expect(total).to.equal(2)
494 expect(data).to.be.an('array')
495 expect(data).to.have.lengthOf(2)
498 const comment = data[0]
499 expect(comment.inReplyToCommentId).to.be.null
500 expect(comment.text).equal('my super first comment')
501 expect(comment.videoId).to.equal(video4.id)
502 expect(comment.id).to.equal(comment.threadId)
503 expect(comment.account.name).to.equal('root')
504 expect(comment.account.host).to.equal(servers[2].host)
505 expect(comment.totalReplies).to.equal(3)
506 expect(dateIsValid(comment.createdAt as string)).to.be.true
507 expect(dateIsValid(comment.updatedAt as string)).to.be.true
509 const threadId = comment.threadId
511 const tree = await servers[0].comments.getThread({ videoId: video4.id, threadId })
512 expect(tree.comment.text).equal('my super first comment')
513 expect(tree.children).to.have.lengthOf(2)
515 const firstChild = tree.children[0]
516 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
517 expect(firstChild.children).to.have.lengthOf(1)
519 const childOfFirstChild = firstChild.children[0]
520 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
521 expect(childOfFirstChild.children).to.have.lengthOf(0)
523 const secondChild = tree.children[1]
524 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
525 expect(secondChild.children).to.have.lengthOf(0)
529 const deletedComment = data[1]
530 expect(deletedComment).to.not.be.undefined
531 expect(deletedComment.isDeleted).to.be.true
532 expect(deletedComment.deletedAt).to.not.be.null
533 expect(deletedComment.text).to.equal('')
534 expect(deletedComment.inReplyToCommentId).to.be.null
535 expect(deletedComment.account).to.be.null
536 expect(deletedComment.totalReplies).to.equal(2)
537 expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true
539 const tree = await servers[0].comments.getThread({ videoId: video4.id, threadId: deletedComment.threadId })
540 const [ commentRoot, deletedChildRoot ] = tree.children
542 expect(deletedChildRoot).to.not.be.undefined
543 expect(deletedChildRoot.comment.isDeleted).to.be.true
544 expect(deletedChildRoot.comment.deletedAt).to.not.be.null
545 expect(deletedChildRoot.comment.text).to.equal('')
546 expect(deletedChildRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
547 expect(deletedChildRoot.comment.account).to.be.null
548 expect(deletedChildRoot.children).to.have.lengthOf(1)
550 const answerToDeletedChild = deletedChildRoot.children[0]
551 expect(answerToDeletedChild.comment).to.not.be.undefined
552 expect(answerToDeletedChild.comment.inReplyToCommentId).to.equal(deletedChildRoot.comment.id)
553 expect(answerToDeletedChild.comment.text).to.equal('my second answer to deleted')
554 expect(answerToDeletedChild.comment.account.name).to.equal('root')
556 expect(commentRoot.comment).to.not.be.undefined
557 expect(commentRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
558 expect(commentRoot.comment.text).to.equal('answer to deleted')
559 expect(commentRoot.comment.account.name).to.equal('root')
563 it('Should have propagated captions', async function () {
564 const body = await servers[0].captions.list({ videoId: video4.id })
565 expect(body.total).to.equal(1)
566 expect(body.data).to.have.lengthOf(1)
568 const caption1 = body.data[0]
569 expect(caption1.language.id).to.equal('ar')
570 expect(caption1.language.label).to.equal('Arabic')
571 expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/.+-ar.vtt$'))
572 await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
575 it('Should unfollow server 3 on server 1 and does not list server 3 videos', async function () {
578 await servers[0].follows.unfollow({ target: servers[2] })
580 await waitJobs(servers)
582 const { total } = await servers[0].videos.list()
583 expect(total).to.equal(1)
587 describe('Should propagate data on a new channel follow', function () {
589 before(async function () {
592 await servers[2].videos.upload({ attributes: { name: 'server3-7' } })
594 await waitJobs(servers)
596 const video = await servers[0].videos.find({ name: 'server3-7' })
597 expect(video).to.not.exist
600 it('Should have propagated channel video', async function () {
603 await servers[0].follows.follow({ handles: [ 'root_channel@' + servers[2].host ] })
605 await waitJobs(servers)
607 const video = await servers[0].videos.find({ name: 'server3-7' })
609 expect(video).to.exist
613 after(async function () {
614 await cleanupTests(servers)