]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/feeds/feeds.ts
Use source sans 3 font
[github/Chocobozzz/PeerTube.git] / server / tests / feeds / feeds.ts
CommitLineData
a1587156 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
fe3a55b0 2
fe3a55b0 3import 'mocha'
696d83fd
C
4import * as chai from 'chai'
5import * as libxmljs from 'libxmljs'
6import {
7 addAccountToAccountBlocklist,
8 addAccountToServerBlocklist,
9 removeAccountFromServerBlocklist
10} from '@shared/extra-utils/users/blocklist'
18490b07 11import { addUserSubscription, listUserSubscriptionVideos } from '@shared/extra-utils/users/user-subscriptions'
696d83fd 12import { VideoPrivacy } from '@shared/models'
18490b07 13import { ScopedToken } from '@shared/models/users/user-scoped-token'
fe3a55b0 14import {
7c3b7976 15 cleanupTests,
662fb3ab 16 createUser,
fe3a55b0
C
17 doubleFollow,
18 flushAndRunMultipleServers,
18490b07 19 flushAndRunServer,
57cfff78
C
20 getJSONfeed,
21 getMyUserInformation,
18490b07 22 getUserScopedTokens,
fe3a55b0 23 getXMLfeed,
18490b07 24 renewUserScopedTokens,
fe3a55b0
C
25 ServerInfo,
26 setAccessTokensToServers,
57cfff78 27 uploadVideo,
696d83fd 28 uploadVideoAndGetId,
18490b07 29 userLogin
94565d52 30} from '../../../shared/extra-utils'
94565d52 31import { waitJobs } from '../../../shared/extra-utils/server/jobs'
696d83fd 32import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments'
662fb3ab 33import { User } from '../../../shared/models/users'
f2eb23cd 34import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
fe3a55b0
C
35
36chai.use(require('chai-xml'))
37chai.use(require('chai-json-schema'))
38chai.config.includeStack = true
39const expect = chai.expect
40
41describe('Test syndication feeds', () => {
42 let servers: ServerInfo[] = []
97816649 43 let serverHLSOnly: ServerInfo
662fb3ab 44 let userAccessToken: string
57cfff78
C
45 let rootAccountId: number
46 let rootChannelId: number
47 let userAccountId: number
48 let userChannelId: number
afff310e 49 let userFeedToken: string
fe3a55b0
C
50
51 before(async function () {
52 this.timeout(120000)
53
54 // Run servers
55 servers = await flushAndRunMultipleServers(2)
97816649
C
56 serverHLSOnly = await flushAndRunServer(3, {
57 transcoding: {
58 enabled: true,
59 webtorrent: { enabled: false },
60 hls: { enabled: true }
61 }
62 })
fe3a55b0 63
97816649 64 await setAccessTokensToServers([ ...servers, serverHLSOnly ])
fe3a55b0
C
65 await doubleFollow(servers[0], servers[1])
66
662fb3ab
C
67 {
68 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
69 const user: User = res.body
57cfff78
C
70 rootAccountId = user.account.id
71 rootChannelId = user.videoChannels[0].id
fe3a55b0 72 }
fe3a55b0 73
662fb3ab
C
74 {
75 const attr = { username: 'john', password: 'password' }
a1587156 76 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password })
662fb3ab
C
77 userAccessToken = await userLogin(servers[0], attr)
78
79 const res = await getMyUserInformation(servers[0].url, userAccessToken)
80 const user: User = res.body
57cfff78
C
81 userAccountId = user.account.id
82 userChannelId = user.videoChannels[0].id
afff310e
RK
83
84 const res2 = await getUserScopedTokens(servers[0].url, userAccessToken)
85 const token: ScopedToken = res2.body
86 userFeedToken = token.feedToken
662fb3ab
C
87 }
88
89 {
a1587156 90 await uploadVideo(servers[0].url, userAccessToken, { name: 'user video' })
662fb3ab
C
91 }
92
93 {
94 const videoAttributes = {
95 name: 'my super name for server 1',
96 description: 'my super description for server 1',
97 fixture: 'video_short.webm'
98 }
a1587156 99 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
662fb3ab
C
100 const videoId = res.body.video.id
101
a1587156
C
102 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 1')
103 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 2')
662fb3ab 104 }
fe3a55b0 105
68b6fd21
C
106 {
107 const videoAttributes = { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED }
108 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
109 const videoId = res.body.video.id
110
111 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'comment on unlisted video')
112 }
113
3cd0734f 114 await waitJobs(servers)
fe3a55b0
C
115 })
116
117 describe('All feed', function () {
118
119 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () {
120 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
a1587156 121 const rss = await getXMLfeed(servers[0].url, feed)
fe3a55b0
C
122 expect(rss.text).xml.to.be.valid()
123
a1587156 124 const atom = await getXMLfeed(servers[0].url, feed, 'atom')
fe3a55b0
C
125 expect(atom.text).xml.to.be.valid()
126 }
127 })
128
129 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () {
130 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
a1587156
C
131 const json = await getJSONfeed(servers[0].url, feed)
132 expect(JSON.parse(json.text)).to.be.jsonSchema({ type: 'object' })
fe3a55b0
C
133 }
134 })
135 })
136
137 describe('Videos feed', function () {
97816649 138
fe3a55b0
C
139 it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () {
140 for (const server of servers) {
141 const rss = await getXMLfeed(server.url, 'videos')
142 const xmlDoc = libxmljs.parseXmlString(rss.text)
143 const xmlEnclosure = xmlDoc.get('/rss/channel/item/enclosure')
144 expect(xmlEnclosure).to.exist
145 expect(xmlEnclosure.attr('type').value()).to.be.equal('application/x-bittorrent')
146 expect(xmlEnclosure.attr('length').value()).to.be.equal('218910')
147 expect(xmlEnclosure.attr('url').value()).to.contain('720.torrent')
148 }
149 })
150
151 it('Should contain a valid \'attachments\' object (covers JSON feed 1.0 endpoint)', async function () {
152 for (const server of servers) {
153 const json = await getJSONfeed(server.url, 'videos')
154 const jsonObj = JSON.parse(json.text)
662fb3ab 155 expect(jsonObj.items.length).to.be.equal(2)
a1587156
C
156 expect(jsonObj.items[0].attachments).to.exist
157 expect(jsonObj.items[0].attachments.length).to.be.eq(1)
158 expect(jsonObj.items[0].attachments[0].mime_type).to.be.eq('application/x-bittorrent')
159 expect(jsonObj.items[0].attachments[0].size_in_bytes).to.be.eq(218910)
160 expect(jsonObj.items[0].attachments[0].url).to.contain('720.torrent')
fe3a55b0
C
161 }
162 })
662fb3ab
C
163
164 it('Should filter by account', async function () {
57cfff78
C
165 {
166 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId })
167 const jsonObj = JSON.parse(json.text)
168 expect(jsonObj.items.length).to.be.equal(1)
a1587156
C
169 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
170 expect(jsonObj.items[0].author.name).to.equal('root')
57cfff78
C
171 }
172
173 {
174 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId })
175 const jsonObj = JSON.parse(json.text)
176 expect(jsonObj.items.length).to.be.equal(1)
a1587156
C
177 expect(jsonObj.items[0].title).to.equal('user video')
178 expect(jsonObj.items[0].author.name).to.equal('john')
57cfff78
C
179 }
180
662fb3ab
C
181 for (const server of servers) {
182 {
57cfff78 183 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port })
662fb3ab
C
184 const jsonObj = JSON.parse(json.text)
185 expect(jsonObj.items.length).to.be.equal(1)
a1587156 186 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
662fb3ab
C
187 }
188
189 {
57cfff78 190 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port })
662fb3ab
C
191 const jsonObj = JSON.parse(json.text)
192 expect(jsonObj.items.length).to.be.equal(1)
a1587156 193 expect(jsonObj.items[0].title).to.equal('user video')
662fb3ab
C
194 }
195 }
57cfff78 196 })
662fb3ab 197
57cfff78 198 it('Should filter by video channel', async function () {
662fb3ab 199 {
57cfff78 200 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId })
662fb3ab
C
201 const jsonObj = JSON.parse(json.text)
202 expect(jsonObj.items.length).to.be.equal(1)
a1587156
C
203 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
204 expect(jsonObj.items[0].author.name).to.equal('root')
662fb3ab
C
205 }
206
207 {
57cfff78 208 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId })
662fb3ab
C
209 const jsonObj = JSON.parse(json.text)
210 expect(jsonObj.items.length).to.be.equal(1)
a1587156
C
211 expect(jsonObj.items[0].title).to.equal('user video')
212 expect(jsonObj.items[0].author.name).to.equal('john')
662fb3ab 213 }
662fb3ab 214
662fb3ab
C
215 for (const server of servers) {
216 {
57cfff78 217 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port })
662fb3ab
C
218 const jsonObj = JSON.parse(json.text)
219 expect(jsonObj.items.length).to.be.equal(1)
a1587156 220 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
662fb3ab
C
221 }
222
223 {
57cfff78 224 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port })
662fb3ab
C
225 const jsonObj = JSON.parse(json.text)
226 expect(jsonObj.items.length).to.be.equal(1)
a1587156 227 expect(jsonObj.items[0].title).to.equal('user video')
662fb3ab
C
228 }
229 }
662fb3ab 230 })
97816649
C
231
232 it('Should correctly have videos feed with HLS only', async function () {
233 this.timeout(120000)
234
235 await uploadVideo(serverHLSOnly.url, serverHLSOnly.accessToken, { name: 'hls only video' })
236
237 await waitJobs([ serverHLSOnly ])
238
239 const json = await getJSONfeed(serverHLSOnly.url, 'videos')
240 const jsonObj = JSON.parse(json.text)
241 expect(jsonObj.items.length).to.be.equal(1)
242 expect(jsonObj.items[0].attachments).to.exist
243 expect(jsonObj.items[0].attachments.length).to.be.eq(4)
244
245 for (let i = 0; i < 4; i++) {
246 expect(jsonObj.items[0].attachments[i].mime_type).to.be.eq('application/x-bittorrent')
247 expect(jsonObj.items[0].attachments[i].size_in_bytes).to.be.greaterThan(0)
248 expect(jsonObj.items[0].attachments[i].url).to.exist
249 }
250 })
fe3a55b0
C
251 })
252
253 describe('Video comments feed', function () {
68b6fd21
C
254
255 it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted videos', async function () {
fe3a55b0
C
256 for (const server of servers) {
257 const json = await getJSONfeed(server.url, 'video-comments')
258
259 const jsonObj = JSON.parse(json.text)
260 expect(jsonObj.items.length).to.be.equal(2)
a1587156
C
261 expect(jsonObj.items[0].html_content).to.equal('super comment 2')
262 expect(jsonObj.items[1].html_content).to.equal('super comment 1')
fe3a55b0
C
263 }
264 })
1df8a4d7
C
265
266 it('Should not list comments from muted accounts or instances', async function () {
696d83fd
C
267 this.timeout(30000)
268
269 const remoteHandle = 'root@localhost:' + servers[0].port
270
271 await addAccountToServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
1df8a4d7
C
272
273 {
274 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 2 })
275 const jsonObj = JSON.parse(json.text)
276 expect(jsonObj.items.length).to.be.equal(0)
277 }
278
696d83fd
C
279 await removeAccountFromServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
280
281 {
282 const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'server 2' })).uuid
283 await waitJobs(servers)
284 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'super comment')
285 await waitJobs(servers)
286
287 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 3 })
288 const jsonObj = JSON.parse(json.text)
289 expect(jsonObj.items.length).to.be.equal(3)
290 }
291
292 await addAccountToAccountBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
293
294 {
295 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 4 })
296 const jsonObj = JSON.parse(json.text)
297 expect(jsonObj.items.length).to.be.equal(2)
298 }
1df8a4d7 299 })
fe3a55b0
C
300 })
301
afff310e 302 describe('Video feed from my subscriptions', function () {
18490b07
C
303 let feeduserAccountId: number
304 let feeduserFeedToken: string
afff310e
RK
305
306 it('Should list no videos for a user with no videos and no subscriptions', async function () {
afff310e
RK
307 const attr = { username: 'feeduser', password: 'password' }
308 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password })
309 const feeduserAccessToken = await userLogin(servers[0], attr)
310
311 {
312 const res = await getMyUserInformation(servers[0].url, feeduserAccessToken)
313 const user: User = res.body
314 feeduserAccountId = user.account.id
315 }
316
317 {
318 const res = await getUserScopedTokens(servers[0].url, feeduserAccessToken)
319 const token: ScopedToken = res.body
320 feeduserFeedToken = token.feedToken
321 }
322
323 {
324 const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken)
325 expect(res.body.total).to.equal(0)
326
5beb89f2 327 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken })
afff310e
RK
328 const jsonObj = JSON.parse(json.text)
329 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
330 }
331 })
332
18490b07 333 it('Should fail with an invalid token', async function () {
f2eb23cd 334 await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: 'toto' }, HttpStatusCode.FORBIDDEN_403)
18490b07
C
335 })
336
337 it('Should fail with a token of another user', async function () {
f2eb23cd
RK
338 await getJSONfeed(
339 servers[0].url,
340 'subscriptions',
341 { accountId: feeduserAccountId, token: userFeedToken },
342 HttpStatusCode.FORBIDDEN_403
343 )
18490b07
C
344 })
345
afff310e 346 it('Should list no videos for a user with videos but no subscriptions', async function () {
18490b07
C
347 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
348 expect(res.body.total).to.equal(0)
afff310e 349
18490b07
C
350 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken })
351 const jsonObj = JSON.parse(json.text)
352 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
afff310e
RK
353 })
354
355 it('Should list self videos for a user with a subscription to themselves', async function () {
356 this.timeout(30000)
357
358 await addUserSubscription(servers[0].url, userAccessToken, 'john_channel@localhost:' + servers[0].port)
359 await waitJobs(servers)
360
361 {
362 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
363 expect(res.body.total).to.equal(1)
364 expect(res.body.data[0].name).to.equal('user video')
365
5beb89f2 366 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 })
afff310e
RK
367 const jsonObj = JSON.parse(json.text)
368 expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's
369 }
370 })
371
372 it('Should list videos of a user\'s subscription', async function () {
373 this.timeout(30000)
374
375 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[0].port)
376 await waitJobs(servers)
377
378 {
379 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
380 expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription")
381
5beb89f2 382 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 })
afff310e
RK
383 const jsonObj = JSON.parse(json.text)
384 expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's
385 }
386 })
387
18490b07
C
388 it('Should renew the token, and so have an invalid old token', async function () {
389 await renewUserScopedTokens(servers[0].url, userAccessToken)
390
f2eb23cd
RK
391 await getJSONfeed(
392 servers[0].url,
393 'subscriptions',
394 { accountId: userAccountId, token: userFeedToken, version: 3 },
395 HttpStatusCode.FORBIDDEN_403
396 )
18490b07
C
397 })
398
399 it('Should succeed with the new token', async function () {
400 const res2 = await getUserScopedTokens(servers[0].url, userAccessToken)
401 const token: ScopedToken = res2.body
402 userFeedToken = token.feedToken
403
404 await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 4 })
405 })
406
afff310e
RK
407 })
408
7c3b7976 409 after(async function () {
97816649 410 await cleanupTests([ ...servers, serverHLSOnly ])
fe3a55b0
C
411 })
412})