Shared utils -> extra-utils
[oweals/peertube.git] / server / tests / api / server / follows.ts
1 /* tslint:disable:no-unused-expression */
2
3 import * as chai from 'chai'
4 import 'mocha'
5 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
6 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
7 import { completeVideoCheck } from '../../../../shared/extra-utils'
8 import {
9   flushAndRunMultipleServers,
10   getVideosList,
11   killallServers,
12   ServerInfo,
13   setAccessTokensToServers,
14   uploadVideo
15 } from '../../../../shared/extra-utils/index'
16 import { dateIsValid } from '../../../../shared/extra-utils/miscs/miscs'
17 import {
18   follow,
19   getFollowersListPaginationAndSort,
20   getFollowingListPaginationAndSort,
21   unfollow
22 } from '../../../../shared/extra-utils/server/follows'
23 import { expectAccountFollows } from '../../../../shared/extra-utils/users/accounts'
24 import { userLogin } from '../../../../shared/extra-utils/users/login'
25 import { createUser } from '../../../../shared/extra-utils/users/users'
26 import {
27   addVideoCommentReply,
28   addVideoCommentThread,
29   getVideoCommentThreads,
30   getVideoThreadComments
31 } from '../../../../shared/extra-utils/videos/video-comments'
32 import { rateVideo } from '../../../../shared/extra-utils/videos/videos'
33 import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
34 import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/extra-utils/videos/video-captions'
35 import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
36
37 const expect = chai.expect
38
39 describe('Test follows', function () {
40   let servers: ServerInfo[] = []
41
42   before(async function () {
43     this.timeout(30000)
44
45     servers = await flushAndRunMultipleServers(3)
46
47     // Get the access tokens
48     await setAccessTokensToServers(servers)
49   })
50
51   it('Should not have followers', async function () {
52     for (const server of servers) {
53       const res = await getFollowersListPaginationAndSort(server.url, 0, 5, 'createdAt')
54       const follows = res.body.data
55
56       expect(res.body.total).to.equal(0)
57       expect(follows).to.be.an('array')
58       expect(follows.length).to.equal(0)
59     }
60   })
61
62   it('Should not have following', async function () {
63     for (const server of servers) {
64       const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt')
65       const follows = res.body.data
66
67       expect(res.body.total).to.equal(0)
68       expect(follows).to.be.an('array')
69       expect(follows.length).to.equal(0)
70     }
71   })
72
73   it('Should have server 1 following server 2 and 3', async function () {
74     this.timeout(30000)
75
76     await follow(servers[0].url, [ servers[1].url, servers[2].url ], servers[0].accessToken)
77
78     await waitJobs(servers)
79   })
80
81   it('Should have 2 followings on server 1', async function () {
82     let res = await getFollowingListPaginationAndSort(servers[0].url, 0, 1, 'createdAt')
83     let follows = res.body.data
84
85     expect(res.body.total).to.equal(2)
86     expect(follows).to.be.an('array')
87     expect(follows.length).to.equal(1)
88
89     res = await getFollowingListPaginationAndSort(servers[0].url, 1, 1, 'createdAt')
90     follows = follows.concat(res.body.data)
91
92     const server2Follow = follows.find(f => f.following.host === 'localhost:9002')
93     const server3Follow = follows.find(f => f.following.host === 'localhost:9003')
94
95     expect(server2Follow).to.not.be.undefined
96     expect(server3Follow).to.not.be.undefined
97     expect(server2Follow.state).to.equal('accepted')
98     expect(server3Follow.state).to.equal('accepted')
99   })
100
101   it('Should search followings on server 1', async function () {
102     {
103       const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':9002')
104       const follows = res.body.data
105
106       expect(res.body.total).to.equal(1)
107       expect(follows.length).to.equal(1)
108       expect(follows[ 0 ].following.host).to.equal('localhost:9002')
109     }
110
111     {
112       const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', 'bla')
113       const follows = res.body.data
114
115       expect(res.body.total).to.equal(0)
116       expect(follows.length).to.equal(0)
117     }
118   })
119
120   it('Should have 0 followings on server 2 and 3', async function () {
121     for (const server of [ servers[1], servers[2] ]) {
122       const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt')
123       const follows = res.body.data
124
125       expect(res.body.total).to.equal(0)
126       expect(follows).to.be.an('array')
127       expect(follows.length).to.equal(0)
128     }
129   })
130
131   it('Should have 1 followers on server 2 and 3', async function () {
132     for (const server of [ servers[1], servers[2] ]) {
133       let res = await getFollowersListPaginationAndSort(server.url, 0, 1, 'createdAt')
134
135       let follows = res.body.data
136       expect(res.body.total).to.equal(1)
137       expect(follows).to.be.an('array')
138       expect(follows.length).to.equal(1)
139       expect(follows[0].follower.host).to.equal('localhost:9001')
140     }
141   })
142
143   it('Should search followers on server 2', async function () {
144     {
145       const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', '9001')
146       const follows = res.body.data
147
148       expect(res.body.total).to.equal(1)
149       expect(follows.length).to.equal(1)
150       expect(follows[ 0 ].following.host).to.equal('localhost:9003')
151     }
152
153     {
154       const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', 'bla')
155       const follows = res.body.data
156
157       expect(res.body.total).to.equal(0)
158       expect(follows.length).to.equal(0)
159     }
160   })
161
162   it('Should have 0 followers on server 1', async function () {
163     const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 5, 'createdAt')
164     const follows = res.body.data
165
166     expect(res.body.total).to.equal(0)
167     expect(follows).to.be.an('array')
168     expect(follows.length).to.equal(0)
169   })
170
171   it('Should have the correct follows counts', async function () {
172     await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
173     await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
174     await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
175
176     // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
177     await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
178     await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
179
180     await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 1)
181     await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
182   })
183
184   it('Should unfollow server 3 on server 1', async function () {
185     this.timeout(5000)
186
187     await unfollow(servers[0].url, servers[0].accessToken, servers[2])
188
189     await waitJobs(servers)
190   })
191
192   it('Should not follow server 3 on server 1 anymore', async function () {
193     const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
194     let follows = res.body.data
195
196     expect(res.body.total).to.equal(1)
197     expect(follows).to.be.an('array')
198     expect(follows.length).to.equal(1)
199
200     expect(follows[0].following.host).to.equal('localhost:9002')
201   })
202
203   it('Should not have server 1 as follower on server 3 anymore', async function () {
204     const res = await getFollowersListPaginationAndSort(servers[2].url, 0, 1, 'createdAt')
205
206     let follows = res.body.data
207     expect(res.body.total).to.equal(0)
208     expect(follows).to.be.an('array')
209     expect(follows.length).to.equal(0)
210   })
211
212   it('Should have the correct follows counts 2', async function () {
213     await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 1)
214     await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
215
216     await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
217     await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
218
219     await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 0)
220     await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 0, 0)
221   })
222
223   it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
224     this.timeout(35000)
225
226     await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'server2' })
227     await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3' })
228
229     await waitJobs(servers)
230
231     let res = await getVideosList(servers[0].url)
232     expect(res.body.total).to.equal(1)
233     expect(res.body.data[0].name).to.equal('server2')
234
235     res = await getVideosList(servers[1].url)
236     expect(res.body.total).to.equal(1)
237     expect(res.body.data[0].name).to.equal('server2')
238
239     res = await getVideosList(servers[2].url)
240     expect(res.body.total).to.equal(1)
241     expect(res.body.data[0].name).to.equal('server3')
242   })
243
244   describe('Should propagate data on a new following', function () {
245     let video4: Video
246
247     before(async function () {
248       this.timeout(20000)
249
250       const video4Attributes = {
251         name: 'server3-4',
252         category: 2,
253         nsfw: true,
254         licence: 6,
255         tags: [ 'tag1', 'tag2', 'tag3' ]
256       }
257
258       await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-2' })
259       await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-3' })
260       await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4Attributes)
261       await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-5' })
262       await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-6' })
263
264       {
265         const user = { username: 'captain', password: 'password' }
266         await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password })
267         const userAccessToken = await userLogin(servers[ 2 ], user)
268
269         const resVideos = await getVideosList(servers[ 2 ].url)
270         video4 = resVideos.body.data.find(v => v.name === 'server3-4')
271
272         {
273           await rateVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, 'like')
274           await rateVideo(servers[ 2 ].url, userAccessToken, video4.id, 'dislike')
275         }
276
277         {
278           const text = 'my super first comment'
279           const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text)
280           const threadId = res.body.comment.id
281
282           const text1 = 'my super answer to thread 1'
283           const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1)
284           const childCommentId = childCommentRes.body.comment.id
285
286           const text2 = 'my super answer to answer of thread 1'
287           await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2)
288
289           const text3 = 'my second answer to thread 1'
290           await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3)
291         }
292
293         {
294           await createVideoCaption({
295             url: servers[2].url,
296             accessToken: servers[2].accessToken,
297             language: 'ar',
298             videoId: video4.id,
299             fixture: 'subtitle-good2.vtt'
300           })
301         }
302       }
303
304       await waitJobs(servers)
305
306       // Server 1 follows server 3
307       await follow(servers[ 0 ].url, [ servers[ 2 ].url ], servers[ 0 ].accessToken)
308
309       await waitJobs(servers)
310     })
311
312     it('Should have the correct follows counts 3', async function () {
313       await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
314       await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
315       await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
316
317       await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
318       await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
319
320       await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 2)
321       await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
322     })
323
324     it('Should have propagated videos', async function () {
325       const res = await getVideosList(servers[ 0 ].url)
326       expect(res.body.total).to.equal(7)
327
328       const video2 = res.body.data.find(v => v.name === 'server3-2')
329       video4 = res.body.data.find(v => v.name === 'server3-4')
330       const video6 = res.body.data.find(v => v.name === 'server3-6')
331
332       expect(video2).to.not.be.undefined
333       expect(video4).to.not.be.undefined
334       expect(video6).to.not.be.undefined
335
336       const isLocal = false
337       const checkAttributes = {
338         name: 'server3-4',
339         category: 2,
340         licence: 6,
341         language: 'zh',
342         nsfw: true,
343         description: 'my super description',
344         support: 'my super support text',
345         account: {
346           name: 'root',
347           host: 'localhost:9003'
348         },
349         isLocal,
350         commentsEnabled: true,
351         downloadEnabled: true,
352         duration: 5,
353         tags: [ 'tag1', 'tag2', 'tag3' ],
354         privacy: VideoPrivacy.PUBLIC,
355         likes: 1,
356         dislikes: 1,
357         channel: {
358           displayName: 'Main root channel',
359           name: 'root_channel',
360           description: '',
361           isLocal
362         },
363         fixture: 'video_short.webm',
364         files: [
365           {
366             resolution: 720,
367             size: 218910
368           }
369         ]
370       }
371       await completeVideoCheck(servers[ 0 ].url, video4, checkAttributes)
372     })
373
374     it('Should have propagated comments', async function () {
375       const res1 = await getVideoCommentThreads(servers[0].url, video4.id, 0, 5)
376
377       expect(res1.body.total).to.equal(1)
378       expect(res1.body.data).to.be.an('array')
379       expect(res1.body.data).to.have.lengthOf(1)
380
381       const comment: VideoComment = res1.body.data[0]
382       expect(comment.inReplyToCommentId).to.be.null
383       expect(comment.text).equal('my super first comment')
384       expect(comment.videoId).to.equal(video4.id)
385       expect(comment.id).to.equal(comment.threadId)
386       expect(comment.account.name).to.equal('root')
387       expect(comment.account.host).to.equal('localhost:9003')
388       expect(comment.totalReplies).to.equal(3)
389       expect(dateIsValid(comment.createdAt as string)).to.be.true
390       expect(dateIsValid(comment.updatedAt as string)).to.be.true
391
392       const threadId = comment.threadId
393
394       const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId)
395
396       const tree: VideoCommentThreadTree = res2.body
397       expect(tree.comment.text).equal('my super first comment')
398       expect(tree.children).to.have.lengthOf(2)
399
400       const firstChild = tree.children[0]
401       expect(firstChild.comment.text).to.equal('my super answer to thread 1')
402       expect(firstChild.children).to.have.lengthOf(1)
403
404       const childOfFirstChild = firstChild.children[0]
405       expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
406       expect(childOfFirstChild.children).to.have.lengthOf(0)
407
408       const secondChild = tree.children[1]
409       expect(secondChild.comment.text).to.equal('my second answer to thread 1')
410       expect(secondChild.children).to.have.lengthOf(0)
411     })
412
413     it('Should have propagated captions', async function () {
414       const res = await listVideoCaptions(servers[0].url, video4.id)
415       expect(res.body.total).to.equal(1)
416       expect(res.body.data).to.have.lengthOf(1)
417
418       const caption1: VideoCaption = res.body.data[0]
419       expect(caption1.language.id).to.equal('ar')
420       expect(caption1.language.label).to.equal('Arabic')
421       expect(caption1.captionPath).to.equal('/static/video-captions/' + video4.uuid + '-ar.vtt')
422       await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
423     })
424
425     it('Should unfollow server 3 on server 1 and does not list server 3 videos', async function () {
426       this.timeout(5000)
427
428       await unfollow(servers[0].url, servers[0].accessToken, servers[2])
429
430       await waitJobs(servers)
431
432       let res = await getVideosList(servers[ 0 ].url)
433       expect(res.body.total).to.equal(1)
434     })
435
436   })
437
438   after(async function () {
439     killallServers(servers)
440   })
441 })