Federated video streaming platform using ActivityPub and P2P in the web browser with Angular. https://joinpeertube.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

615 lines
23 KiB

  1. /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
  2. import 'mocha'
  3. import * as chai from 'chai'
  4. import { completeVideoCheck, dateIsValid, expectAccountFollows, expectChannelsFollows, testCaptionFile } from '@server/tests/shared'
  5. import { VideoCreateResult, VideoPrivacy } from '@shared/models'
  6. import { cleanupTests, createMultipleServers, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
  7. const expect = chai.expect
  8. describe('Test follows', function () {
  9. let servers: PeerTubeServer[] = []
  10. before(async function () {
  11. this.timeout(120000)
  12. servers = await createMultipleServers(3)
  13. // Get the access tokens
  14. await setAccessTokensToServers(servers)
  15. })
  16. describe('Data propagation after follow', function () {
  17. it('Should not have followers/followings', async function () {
  18. for (const server of servers) {
  19. const bodies = await Promise.all([
  20. server.follows.getFollowers({ start: 0, count: 5, sort: 'createdAt' }),
  21. server.follows.getFollowings({ start: 0, count: 5, sort: 'createdAt' })
  22. ])
  23. for (const body of bodies) {
  24. expect(body.total).to.equal(0)
  25. const follows = body.data
  26. expect(follows).to.be.an('array')
  27. expect(follows).to.have.lengthOf(0)
  28. }
  29. }
  30. })
  31. it('Should have server 1 following root account of server 2 and server 3', async function () {
  32. this.timeout(30000)
  33. await servers[0].follows.follow({
  34. hosts: [ servers[2].url ],
  35. handles: [ 'root@' + servers[1].host ]
  36. })
  37. await waitJobs(servers)
  38. })
  39. it('Should have 2 followings on server 1', async function () {
  40. const body = await servers[0].follows.getFollowings({ start: 0, count: 1, sort: 'createdAt' })
  41. expect(body.total).to.equal(2)
  42. let follows = body.data
  43. expect(follows).to.be.an('array')
  44. expect(follows).to.have.lengthOf(1)
  45. const body2 = await servers[0].follows.getFollowings({ start: 1, count: 1, sort: 'createdAt' })
  46. follows = follows.concat(body2.data)
  47. const server2Follow = follows.find(f => f.following.host === servers[1].host)
  48. const server3Follow = follows.find(f => f.following.host === servers[2].host)
  49. expect(server2Follow).to.not.be.undefined
  50. expect(server2Follow.following.name).to.equal('root')
  51. expect(server2Follow.state).to.equal('accepted')
  52. expect(server3Follow).to.not.be.undefined
  53. expect(server3Follow.following.name).to.equal('peertube')
  54. expect(server3Follow.state).to.equal('accepted')
  55. })
  56. it('Should have 0 followings on server 2 and 3', async function () {
  57. for (const server of [ servers[1], servers[2] ]) {
  58. const body = await server.follows.getFollowings({ start: 0, count: 5, sort: 'createdAt' })
  59. expect(body.total).to.equal(0)
  60. const follows = body.data
  61. expect(follows).to.be.an('array')
  62. expect(follows).to.have.lengthOf(0)
  63. }
  64. })
  65. it('Should have 1 followers on server 3', async function () {
  66. const body = await servers[2].follows.getFollowers({ start: 0, count: 1, sort: 'createdAt' })
  67. expect(body.total).to.equal(1)
  68. const follows = body.data
  69. expect(follows).to.be.an('array')
  70. expect(follows).to.have.lengthOf(1)
  71. expect(follows[0].follower.host).to.equal('localhost:' + servers[0].port)
  72. })
  73. it('Should have 0 followers on server 1 and 2', async function () {
  74. for (const server of [ servers[0], servers[1] ]) {
  75. const body = await server.follows.getFollowers({ start: 0, count: 5, sort: 'createdAt' })
  76. expect(body.total).to.equal(0)
  77. const follows = body.data
  78. expect(follows).to.be.an('array')
  79. expect(follows).to.have.lengthOf(0)
  80. }
  81. })
  82. it('Should search/filter followings on server 1', async function () {
  83. const sort = 'createdAt'
  84. const start = 0
  85. const count = 1
  86. {
  87. const search = ':' + servers[1].port
  88. {
  89. const body = await servers[0].follows.getFollowings({ start, count, sort, search })
  90. expect(body.total).to.equal(1)
  91. const follows = body.data
  92. expect(follows).to.have.lengthOf(1)
  93. expect(follows[0].following.host).to.equal(servers[1].host)
  94. }
  95. {
  96. const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'accepted' })
  97. expect(body.total).to.equal(1)
  98. expect(body.data).to.have.lengthOf(1)
  99. }
  100. {
  101. const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'accepted', actorType: 'Person' })
  102. expect(body.total).to.equal(1)
  103. expect(body.data).to.have.lengthOf(1)
  104. }
  105. {
  106. const body = await servers[0].follows.getFollowings({
  107. start,
  108. count,
  109. sort,
  110. search,
  111. state: 'accepted',
  112. actorType: 'Application'
  113. })
  114. expect(body.total).to.equal(0)
  115. expect(body.data).to.have.lengthOf(0)
  116. }
  117. {
  118. const body = await servers[0].follows.getFollowings({ start, count, sort, search, state: 'pending' })
  119. expect(body.total).to.equal(0)
  120. expect(body.data).to.have.lengthOf(0)
  121. }
  122. }
  123. {
  124. const body = await servers[0].follows.getFollowings({ start, count, sort, search: 'root' })
  125. expect(body.total).to.equal(1)
  126. expect(body.data).to.have.lengthOf(1)
  127. }
  128. {
  129. const body = await servers[0].follows.getFollowings({ start, count, sort, search: 'bla' })
  130. expect(body.total).to.equal(0)
  131. expect(body.data).to.have.lengthOf(0)
  132. }
  133. })
  134. it('Should search/filter followers on server 2', async function () {
  135. const start = 0
  136. const count = 5
  137. const sort = 'createdAt'
  138. {
  139. const search = servers[0].port + ''
  140. {
  141. const body = await servers[2].follows.getFollowers({ start, count, sort, search })
  142. expect(body.total).to.equal(1)
  143. const follows = body.data
  144. expect(follows).to.have.lengthOf(1)
  145. expect(follows[0].following.host).to.equal(servers[2].host)
  146. }
  147. {
  148. const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'accepted' })
  149. expect(body.total).to.equal(1)
  150. expect(body.data).to.have.lengthOf(1)
  151. }
  152. {
  153. const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'accepted', actorType: 'Person' })
  154. expect(body.total).to.equal(0)
  155. expect(body.data).to.have.lengthOf(0)
  156. }
  157. {
  158. const body = await servers[2].follows.getFollowers({
  159. start,
  160. count,
  161. sort,
  162. search,
  163. state: 'accepted',
  164. actorType: 'Application'
  165. })
  166. expect(body.total).to.equal(1)
  167. expect(body.data).to.have.lengthOf(1)
  168. }
  169. {
  170. const body = await servers[2].follows.getFollowers({ start, count, sort, search, state: 'pending' })
  171. expect(body.total).to.equal(0)
  172. expect(body.data).to.have.lengthOf(0)
  173. }
  174. }
  175. {
  176. const body = await servers[2].follows.getFollowers({ start, count, sort, search: 'bla' })
  177. expect(body.total).to.equal(0)
  178. const follows = body.data
  179. expect(follows).to.have.lengthOf(0)
  180. }
  181. })
  182. it('Should have the correct follows counts', async function () {
  183. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 2 })
  184. await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
  185. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
  186. // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
  187. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  188. await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
  189. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
  190. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  191. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
  192. })
  193. it('Should unfollow server 3 on server 1', async function () {
  194. this.timeout(15000)
  195. await servers[0].follows.unfollow({ target: servers[2] })
  196. await waitJobs(servers)
  197. })
  198. it('Should not follow server 3 on server 1 anymore', async function () {
  199. const body = await servers[0].follows.getFollowings({ start: 0, count: 2, sort: 'createdAt' })
  200. expect(body.total).to.equal(1)
  201. const follows = body.data
  202. expect(follows).to.be.an('array')
  203. expect(follows).to.have.lengthOf(1)
  204. expect(follows[0].following.host).to.equal(servers[1].host)
  205. })
  206. it('Should not have server 1 as follower on server 3 anymore', async function () {
  207. const body = await servers[2].follows.getFollowers({ start: 0, count: 1, sort: 'createdAt' })
  208. expect(body.total).to.equal(0)
  209. const follows = body.data
  210. expect(follows).to.be.an('array')
  211. expect(follows).to.have.lengthOf(0)
  212. })
  213. it('Should have the correct follows counts after the unfollow', async function () {
  214. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  215. await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
  216. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 0, following: 0 })
  217. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  218. await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 1, following: 0 })
  219. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
  220. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 0 })
  221. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 0, following: 0 })
  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(120000)
  225. await servers[1].videos.upload({ attributes: { name: 'server2' } })
  226. await servers[2].videos.upload({ attributes: { name: 'server3' } })
  227. await waitJobs(servers)
  228. {
  229. const { total, data } = await servers[0].videos.list()
  230. expect(total).to.equal(1)
  231. expect(data[0].name).to.equal('server2')
  232. }
  233. {
  234. const { total, data } = await servers[1].videos.list()
  235. expect(total).to.equal(1)
  236. expect(data[0].name).to.equal('server2')
  237. }
  238. {
  239. const { total, data } = await servers[2].videos.list()
  240. expect(total).to.equal(1)
  241. expect(data[0].name).to.equal('server3')
  242. }
  243. })
  244. it('Should remove account follow', async function () {
  245. this.timeout(15000)
  246. await servers[0].follows.unfollow({ target: 'root@' + servers[1].host })
  247. await waitJobs(servers)
  248. })
  249. it('Should have removed the account follow', async function () {
  250. await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
  251. await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
  252. {
  253. const { total, data } = await servers[0].follows.getFollowings()
  254. expect(total).to.equal(0)
  255. expect(data).to.have.lengthOf(0)
  256. }
  257. {
  258. const { total, data } = await servers[0].videos.list()
  259. expect(total).to.equal(0)
  260. expect(data).to.have.lengthOf(0)
  261. }
  262. })
  263. it('Should follow a channel', async function () {
  264. this.timeout(15000)
  265. await servers[0].follows.follow({
  266. handles: [ 'root_channel@' + servers[1].host ]
  267. })
  268. await waitJobs(servers)
  269. await expectChannelsFollows({ server: servers[0], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
  270. await expectChannelsFollows({ server: servers[1], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
  271. {
  272. const { total, data } = await servers[0].follows.getFollowings()
  273. expect(total).to.equal(1)
  274. expect(data).to.have.lengthOf(1)
  275. }
  276. {
  277. const { total, data } = await servers[0].videos.list()
  278. expect(total).to.equal(1)
  279. expect(data).to.have.lengthOf(1)
  280. }
  281. })
  282. })
  283. describe('Should propagate data on a new server follow', function () {
  284. let video4: VideoCreateResult
  285. before(async function () {
  286. this.timeout(50000)
  287. const video4Attributes = {
  288. name: 'server3-4',
  289. category: 2,
  290. nsfw: true,
  291. licence: 6,
  292. tags: [ 'tag1', 'tag2', 'tag3' ]
  293. }
  294. await servers[2].videos.upload({ attributes: { name: 'server3-2' } })
  295. await servers[2].videos.upload({ attributes: { name: 'server3-3' } })
  296. video4 = await servers[2].videos.upload({ attributes: video4Attributes })
  297. await servers[2].videos.upload({ attributes: { name: 'server3-5' } })
  298. await servers[2].videos.upload({ attributes: { name: 'server3-6' } })
  299. {
  300. const userAccessToken = await servers[2].users.generateUserAndToken('captain')
  301. await servers[2].videos.rate({ id: video4.id, rating: 'like' })
  302. await servers[2].videos.rate({ token: userAccessToken, id: video4.id, rating: 'dislike' })
  303. }
  304. {
  305. await servers[2].comments.createThread({ videoId: video4.id, text: 'my super first comment' })
  306. await servers[2].comments.addReplyToLastThread({ text: 'my super answer to thread 1' })
  307. await servers[2].comments.addReplyToLastReply({ text: 'my super answer to answer of thread 1' })
  308. await servers[2].comments.addReplyToLastThread({ text: 'my second answer to thread 1' })
  309. }
  310. {
  311. const { id: threadId } = await servers[2].comments.createThread({ videoId: video4.id, text: 'will be deleted' })
  312. await servers[2].comments.addReplyToLastThread({ text: 'answer to deleted' })
  313. const { id: replyId } = await servers[2].comments.addReplyToLastThread({ text: 'will also be deleted' })
  314. await servers[2].comments.addReplyToLastReply({ text: 'my second answer to deleted' })
  315. await servers[2].comments.delete({ videoId: video4.id, commentId: threadId })
  316. await servers[2].comments.delete({ videoId: video4.id, commentId: replyId })
  317. }
  318. await servers[2].captions.add({
  319. language: 'ar',
  320. videoId: video4.id,
  321. fixture: 'subtitle-good2.vtt'
  322. })
  323. await waitJobs(servers)
  324. // Server 1 follows server 3
  325. await servers[0].follows.follow({ hosts: [ servers[2].url ] })
  326. await waitJobs(servers)
  327. })
  328. it('Should have the correct follows counts', async function () {
  329. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[0].host, followers: 0, following: 2 })
  330. await expectAccountFollows({ server: servers[0], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
  331. await expectChannelsFollows({ server: servers[0], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
  332. await expectAccountFollows({ server: servers[0], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
  333. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  334. await expectAccountFollows({ server: servers[1], handle: 'peertube@' + servers[1].host, followers: 0, following: 0 })
  335. await expectAccountFollows({ server: servers[1], handle: 'root@' + servers[1].host, followers: 0, following: 0 })
  336. await expectChannelsFollows({ server: servers[1], handle: 'root_channel@' + servers[1].host, followers: 1, following: 0 })
  337. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[0].host, followers: 0, following: 1 })
  338. await expectAccountFollows({ server: servers[2], handle: 'peertube@' + servers[2].host, followers: 1, following: 0 })
  339. })
  340. it('Should have propagated videos', async function () {
  341. const { total, data } = await servers[0].videos.list()
  342. expect(total).to.equal(7)
  343. const video2 = data.find(v => v.name === 'server3-2')
  344. video4 = data.find(v => v.name === 'server3-4')
  345. const video6 = data.find(v => v.name === 'server3-6')
  346. expect(video2).to.not.be.undefined
  347. expect(video4).to.not.be.undefined
  348. expect(video6).to.not.be.undefined
  349. const isLocal = false
  350. const checkAttributes = {
  351. name: 'server3-4',
  352. category: 2,
  353. licence: 6,
  354. language: 'zh',
  355. nsfw: true,
  356. description: 'my super description',
  357. support: 'my super support text',
  358. account: {
  359. name: 'root',
  360. host: servers[2].host
  361. },
  362. isLocal,
  363. commentsEnabled: true,
  364. downloadEnabled: true,
  365. duration: 5,
  366. tags: [ 'tag1', 'tag2', 'tag3' ],
  367. privacy: VideoPrivacy.PUBLIC,
  368. likes: 1,
  369. dislikes: 1,
  370. channel: {
  371. displayName: 'Main root channel',
  372. name: 'root_channel',
  373. description: '',
  374. isLocal
  375. },
  376. fixture: 'video_short.webm',
  377. files: [
  378. {
  379. resolution: 720,
  380. size: 218910
  381. }
  382. ]
  383. }
  384. await completeVideoCheck(servers[0], video4, checkAttributes)
  385. })
  386. it('Should have propagated comments', async function () {
  387. const { total, data } = await servers[0].comments.listThreads({ videoId: video4.id, sort: 'createdAt' })
  388. expect(total).to.equal(2)
  389. expect(data).to.be.an('array')
  390. expect(data).to.have.lengthOf(2)
  391. {
  392. const comment = data[0]
  393. expect(comment.inReplyToCommentId).to.be.null
  394. expect(comment.text).equal('my super first comment')
  395. expect(comment.videoId).to.equal(video4.id)
  396. expect(comment.id).to.equal(comment.threadId)
  397. expect(comment.account.name).to.equal('root')
  398. expect(comment.account.host).to.equal(servers[2].host)
  399. expect(comment.totalReplies).to.equal(3)
  400. expect(dateIsValid(comment.createdAt as string)).to.be.true
  401. expect(dateIsValid(comment.updatedAt as string)).to.be.true
  402. const threadId = comment.threadId
  403. const tree = await servers[0].comments.getThread({ videoId: video4.id, threadId })
  404. expect(tree.comment.text).equal('my super first comment')
  405. expect(tree.children).to.have.lengthOf(2)
  406. const firstChild = tree.children[0]
  407. expect(firstChild.comment.text).to.equal('my super answer to thread 1')
  408. expect(firstChild.children).to.have.lengthOf(1)
  409. const childOfFirstChild = firstChild.children[0]
  410. expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
  411. expect(childOfFirstChild.children).to.have.lengthOf(0)
  412. const secondChild = tree.children[1]
  413. expect(secondChild.comment.text).to.equal('my second answer to thread 1')
  414. expect(secondChild.children).to.have.lengthOf(0)
  415. }
  416. {
  417. const deletedComment = data[1]
  418. expect(deletedComment).to.not.be.undefined
  419. expect(deletedComment.isDeleted).to.be.true
  420. expect(deletedComment.deletedAt).to.not.be.null
  421. expect(deletedComment.text).to.equal('')
  422. expect(deletedComment.inReplyToCommentId).to.be.null
  423. expect(deletedComment.account).to.be.null
  424. expect(deletedComment.totalReplies).to.equal(2)
  425. expect(dateIsValid(deletedComment.deletedAt as string)).to.be.true
  426. const tree = await servers[0].comments.getThread({ videoId: video4.id, threadId: deletedComment.threadId })
  427. const [ commentRoot, deletedChildRoot ] = tree.children
  428. expect(deletedChildRoot).to.not.be.undefined
  429. expect(deletedChildRoot.comment.isDeleted).to.be.true
  430. expect(deletedChildRoot.comment.deletedAt).to.not.be.null
  431. expect(deletedChildRoot.comment.text).to.equal('')
  432. expect(deletedChildRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
  433. expect(deletedChildRoot.comment.account).to.be.null
  434. expect(deletedChildRoot.children).to.have.lengthOf(1)
  435. const answerToDeletedChild = deletedChildRoot.children[0]
  436. expect(answerToDeletedChild.comment).to.not.be.undefined
  437. expect(answerToDeletedChild.comment.inReplyToCommentId).to.equal(deletedChildRoot.comment.id)
  438. expect(answerToDeletedChild.comment.text).to.equal('my second answer to deleted')
  439. expect(answerToDeletedChild.comment.account.name).to.equal('root')
  440. expect(commentRoot.comment).to.not.be.undefined
  441. expect(commentRoot.comment.inReplyToCommentId).to.equal(deletedComment.id)
  442. expect(commentRoot.comment.text).to.equal('answer to deleted')
  443. expect(commentRoot.comment.account.name).to.equal('root')
  444. }
  445. })
  446. it('Should have propagated captions', async function () {
  447. const body = await servers[0].captions.list({ videoId: video4.id })
  448. expect(body.total).to.equal(1)
  449. expect(body.data).to.have.lengthOf(1)
  450. const caption1 = body.data[0]
  451. expect(caption1.language.id).to.equal('ar')
  452. expect(caption1.language.label).to.equal('Arabic')
  453. expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/.+-ar.vtt$'))
  454. await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
  455. })
  456. it('Should unfollow server 3 on server 1 and does not list server 3 videos', async function () {
  457. this.timeout(5000)
  458. await servers[0].follows.unfollow({ target: servers[2] })
  459. await waitJobs(servers)
  460. const { total } = await servers[0].videos.list()
  461. expect(total).to.equal(1)
  462. })
  463. })
  464. describe('Should propagate data on a new channel follow', function () {
  465. before(async function () {
  466. this.timeout(60000)
  467. await servers[2].videos.upload({ attributes: { name: 'server3-7' } })
  468. await waitJobs(servers)
  469. const video = await servers[0].videos.find({ name: 'server3-7' })
  470. expect(video).to.not.exist
  471. })
  472. it('Should have propagated channel video', async function () {
  473. this.timeout(60000)
  474. await servers[0].follows.follow({ handles: [ 'root_channel@' + servers[2].host ] })
  475. await waitJobs(servers)
  476. const video = await servers[0].videos.find({ name: 'server3-7' })
  477. expect(video).to.exist
  478. })
  479. })
  480. after(async function () {
  481. await cleanupTests(servers)
  482. })
  483. })