]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/feeds/feeds.ts
Invalidate cache feed even after server restart
[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
479b2696 3import * as chai from 'chai'
2d28b0c2 4import { XMLParser, XMLValidator } from 'fast-xml-parser'
d0800f76 5import { HttpStatusCode, VideoPrivacy } from '@shared/models'
696d83fd 6import {
7c3b7976 7 cleanupTests,
254d3579
C
8 createMultipleServers,
9 createSingleServer,
4c7e60bc 10 doubleFollow,
20bafcb6 11 makeGetRequest,
5e1bd869 12 makeRawRequest,
254d3579 13 PeerTubeServer,
cb0eda56 14 PluginsCommand,
fe3a55b0 15 setAccessTokensToServers,
d0800f76 16 setDefaultChannelAvatar,
ed14d1eb 17 setDefaultVideoChannel,
7a5c3d77 18 stopFfmpeg,
5f8bd4cb 19 waitJobs
bf54587a 20} from '@shared/server-commands'
fe3a55b0
C
21
22chai.use(require('chai-xml'))
23chai.use(require('chai-json-schema'))
24chai.config.includeStack = true
479b2696 25
e5d771a3
C
26const expect = chai.expect
27
fe3a55b0 28describe('Test syndication feeds', () => {
254d3579
C
29 let servers: PeerTubeServer[] = []
30 let serverHLSOnly: PeerTubeServer
cb0eda56 31
662fb3ab 32 let userAccessToken: string
57cfff78
C
33 let rootAccountId: number
34 let rootChannelId: number
cb0eda56 35
57cfff78
C
36 let userAccountId: number
37 let userChannelId: number
afff310e 38 let userFeedToken: string
cb0eda56 39
7a5c3d77 40 let liveId: string
fe3a55b0
C
41
42 before(async function () {
43 this.timeout(120000)
44
45 // Run servers
254d3579
C
46 servers = await createMultipleServers(2)
47 serverHLSOnly = await createSingleServer(3, {
97816649
C
48 transcoding: {
49 enabled: true,
50 webtorrent: { enabled: false },
51 hls: { enabled: true }
52 }
53 })
fe3a55b0 54
97816649 55 await setAccessTokensToServers([ ...servers, serverHLSOnly ])
d0800f76 56 await setDefaultChannelAvatar(servers[0])
ed14d1eb 57 await setDefaultVideoChannel(servers)
fe3a55b0
C
58 await doubleFollow(servers[0], servers[1])
59
7a5c3d77
C
60 await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
61
662fb3ab 62 {
89d241a7 63 const user = await servers[0].users.getMyInfo()
57cfff78
C
64 rootAccountId = user.account.id
65 rootChannelId = user.videoChannels[0].id
fe3a55b0 66 }
fe3a55b0 67
662fb3ab 68 {
20bafcb6 69 userAccessToken = await servers[0].users.generateUserAndToken('john')
662fb3ab 70
89d241a7 71 const user = await servers[0].users.getMyInfo({ token: userAccessToken })
57cfff78
C
72 userAccountId = user.account.id
73 userChannelId = user.videoChannels[0].id
afff310e 74
89d241a7 75 const token = await servers[0].users.getMyScopedTokens({ token: userAccessToken })
afff310e 76 userFeedToken = token.feedToken
662fb3ab
C
77 }
78
79 {
89d241a7 80 await servers[0].videos.upload({ token: userAccessToken, attributes: { name: 'user video' } })
662fb3ab
C
81 }
82
83 {
d23dd9fb 84 const attributes = {
662fb3ab
C
85 name: 'my super name for server 1',
86 description: 'my super description for server 1',
87 fixture: 'video_short.webm'
88 }
89d241a7 89 const { id } = await servers[0].videos.upload({ attributes })
662fb3ab 90
89d241a7
C
91 await servers[0].comments.createThread({ videoId: id, text: 'super comment 1' })
92 await servers[0].comments.createThread({ videoId: id, text: 'super comment 2' })
662fb3ab 93 }
fe3a55b0 94
68b6fd21 95 {
d23dd9fb 96 const attributes = { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED }
89d241a7 97 const { id } = await servers[0].videos.upload({ attributes })
68b6fd21 98
89d241a7 99 await servers[0].comments.createThread({ videoId: id, text: 'comment on unlisted video' })
68b6fd21
C
100 }
101
cb0eda56
AG
102 await serverHLSOnly.videos.upload({ attributes: { name: 'hls only video' } })
103
104 await waitJobs([ ...servers, serverHLSOnly ])
105
106 await servers[0].plugins.install({ path: PluginsCommand.getPluginTestPath('-podcast-custom-tags') })
fe3a55b0
C
107 })
108
109 describe('All feed', function () {
110
111 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () {
112 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
41e74ec9 113 const rss = await servers[0].feed.getXML({ feed, ignoreCache: true })
c1bc8ee4 114 expect(rss).xml.to.be.valid()
fe3a55b0 115
41e74ec9 116 const atom = await servers[0].feed.getXML({ feed, format: 'atom', ignoreCache: true })
c1bc8ee4 117 expect(atom).xml.to.be.valid()
fe3a55b0
C
118 }
119 })
120
cb0eda56
AG
121 it('Should be well formed XML (covers Podcast endpoint)', async function () {
122 const podcast = await servers[0].feed.getPodcastXML({ ignoreCache: true, channelId: rootChannelId })
123 expect(podcast).xml.to.be.valid()
124 })
125
fe3a55b0
C
126 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () {
127 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
41e74ec9 128 const jsonText = await servers[0].feed.getJSON({ feed, ignoreCache: true })
c1bc8ee4 129 expect(JSON.parse(jsonText)).to.be.jsonSchema({ type: 'object' })
fe3a55b0
C
130 }
131 })
20bafcb6
C
132
133 it('Should serve the endpoint with a classic request', async function () {
134 await makeGetRequest({
135 url: servers[0].url,
136 path: '/feeds/videos.xml',
137 accept: 'application/xml',
138 expectedStatus: HttpStatusCode.OK_200
139 })
140 })
141
20bafcb6
C
142 it('Should refuse to serve the endpoint without accept header', async function () {
143 await makeGetRequest({ url: servers[0].url, path: '/feeds/videos.xml', expectedStatus: HttpStatusCode.NOT_ACCEPTABLE_406 })
144 })
fe3a55b0
C
145 })
146
147 describe('Videos feed', function () {
97816649 148
cb0eda56
AG
149 describe('Podcast feed', function () {
150
151 it('Should contain a valid podcast:alternateEnclosure', async function () {
152 // Since podcast feeds should only work on the server they originate on,
153 // only test the first server where the videos reside
154 const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId })
2d28b0c2 155 expect(XMLValidator.validate(rss)).to.be.true
b70025bf 156
2d28b0c2
C
157 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
158 const xmlDoc = parser.parse(rss)
b70025bf 159
1ed1994f
AG
160 const itemGuid = xmlDoc.rss.channel.item.guid
161 expect(itemGuid).to.exist
162 expect(itemGuid['@_isPermaLink']).to.equal(true)
163
cb0eda56 164 const enclosure = xmlDoc.rss.channel.item.enclosure
b70025bf 165 expect(enclosure).to.exist
cb0eda56
AG
166 const alternateEnclosure = xmlDoc.rss.channel.item['podcast:alternateEnclosure']
167 expect(alternateEnclosure).to.exist
168
169 expect(alternateEnclosure['@_type']).to.equal('video/webm')
170 expect(alternateEnclosure['@_length']).to.equal(218910)
171 expect(alternateEnclosure['@_lang']).to.equal('zh')
172 expect(alternateEnclosure['@_title']).to.equal('720p')
173 expect(alternateEnclosure['@_default']).to.equal(true)
174
175 expect(alternateEnclosure['podcast:source'][0]['@_uri']).to.contain('-720.webm')
176 expect(alternateEnclosure['podcast:source'][0]['@_uri']).to.equal(enclosure['@_url'])
177 expect(alternateEnclosure['podcast:source'][1]['@_uri']).to.contain('-720.torrent')
178 expect(alternateEnclosure['podcast:source'][1]['@_contentType']).to.equal('application/x-bittorrent')
179 expect(alternateEnclosure['podcast:source'][2]['@_uri']).to.contain('magnet:?')
180 })
4393b255 181
cb0eda56
AG
182 it('Should contain a valid podcast:alternateEnclosure with HLS only', async function () {
183 const rss = await serverHLSOnly.feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId })
184 expect(XMLValidator.validate(rss)).to.be.true
fe3a55b0 185
cb0eda56
AG
186 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
187 const xmlDoc = parser.parse(rss)
188
1ed1994f
AG
189 const itemGuid = xmlDoc.rss.channel.item.guid
190 expect(itemGuid).to.exist
191 expect(itemGuid['@_isPermaLink']).to.equal(true)
192
cb0eda56
AG
193 const enclosure = xmlDoc.rss.channel.item.enclosure
194 const alternateEnclosure = xmlDoc.rss.channel.item['podcast:alternateEnclosure']
195 expect(alternateEnclosure).to.exist
196
197 expect(alternateEnclosure['@_type']).to.equal('application/x-mpegURL')
198 expect(alternateEnclosure['@_lang']).to.equal('zh')
199 expect(alternateEnclosure['@_title']).to.equal('HLS')
200 expect(alternateEnclosure['@_default']).to.equal(true)
201
202 expect(alternateEnclosure['podcast:source']['@_uri']).to.contain('-master.m3u8')
203 expect(alternateEnclosure['podcast:source']['@_uri']).to.equal(enclosure['@_url'])
204 })
205
206 it('Should contain a valid podcast:socialInteract', async function () {
207 const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId })
208 expect(XMLValidator.validate(rss)).to.be.true
209
210 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
211 const xmlDoc = parser.parse(rss)
212
213 const item = xmlDoc.rss.channel.item
214 const socialInteract = item['podcast:socialInteract']
215 expect(socialInteract).to.exist
216 expect(socialInteract['@_protocol']).to.equal('activitypub')
217 expect(socialInteract['@_uri']).to.exist
218 expect(socialInteract['@_accountUrl']).to.exist
219 })
220
221 it('Should contain a valid support custom tags for plugins', async function () {
222 const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: userChannelId })
223 expect(XMLValidator.validate(rss)).to.be.true
224
225 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
226 const xmlDoc = parser.parse(rss)
227
228 const fooTag = xmlDoc.rss.channel.fooTag
229 expect(fooTag).to.exist
230 expect(fooTag['@_bar']).to.equal('baz')
231 expect(fooTag['#text']).to.equal(42)
232
233 const bizzBuzzItem = xmlDoc.rss.channel['biz:buzzItem']
234 expect(bizzBuzzItem).to.exist
235
236 let nestedTag = bizzBuzzItem.nestedTag
237 expect(nestedTag).to.exist
238 expect(nestedTag).to.equal('example nested tag')
239
240 const item = xmlDoc.rss.channel.item
241 const fizzTag = item.fizzTag
242 expect(fizzTag).to.exist
243 expect(fizzTag['@_bar']).to.equal('baz')
244 expect(fizzTag['#text']).to.equal(21)
245
246 const bizzBuzz = item['biz:buzz']
247 expect(bizzBuzz).to.exist
248
249 nestedTag = bizzBuzz.nestedTag
250 expect(nestedTag).to.exist
251 expect(nestedTag).to.equal('example nested tag')
252 })
253
254 it('Should contain a valid podcast:liveItem for live streams', async function () {
255 this.timeout(120000)
256
257 const { uuid } = await servers[0].live.create({
258 fields: {
259 name: 'live-0',
260 privacy: VideoPrivacy.PUBLIC,
261 channelId: rootChannelId,
262 permanentLive: false
263 }
264 })
265 liveId = uuid
266
267 const ffmpeg = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveId, copyCodecs: true, fixtureName: 'video_short.mp4' })
268 await servers[0].live.waitUntilPublished({ videoId: liveId })
269
270 const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId })
271 expect(XMLValidator.validate(rss)).to.be.true
272
273 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
274 const xmlDoc = parser.parse(rss)
275 const liveItem = xmlDoc.rss.channel['podcast:liveItem']
276 expect(liveItem.title).to.equal('live-0')
1ed1994f
AG
277 expect(liveItem.guid['@_isPermaLink']).to.equal(false)
278 expect(liveItem.guid['#text']).to.contain(`${uuid}_`)
cb0eda56
AG
279 expect(liveItem['@_status']).to.equal('live')
280
281 const enclosure = liveItem.enclosure
282 const alternateEnclosure = liveItem['podcast:alternateEnclosure']
283 expect(alternateEnclosure).to.exist
284 expect(alternateEnclosure['@_type']).to.equal('application/x-mpegURL')
285 expect(alternateEnclosure['@_title']).to.equal('HLS live stream')
286 expect(alternateEnclosure['@_default']).to.equal(true)
287
288 expect(alternateEnclosure['podcast:source']['@_uri']).to.contain('/master.m3u8')
289 expect(alternateEnclosure['podcast:source']['@_uri']).to.equal(enclosure['@_url'])
290
291 await stopFfmpeg(ffmpeg)
292
293 await servers[0].live.waitUntilEnded({ videoId: liveId })
294
295 await waitJobs(servers)
296 })
fe3a55b0 297 })
662fb3ab 298
cb0eda56 299 describe('JSON feed', function () {
57cfff78 300
cb0eda56
AG
301 it('Should contain a valid \'attachments\' object', async function () {
302 for (const server of servers) {
303 const json = await server.feed.getJSON({ feed: 'videos', ignoreCache: true })
304 const jsonObj = JSON.parse(json)
305 expect(jsonObj.items.length).to.be.equal(2)
306 expect(jsonObj.items[0].attachments).to.exist
307 expect(jsonObj.items[0].attachments.length).to.be.eq(1)
308 expect(jsonObj.items[0].attachments[0].mime_type).to.be.eq('application/x-bittorrent')
309 expect(jsonObj.items[0].attachments[0].size_in_bytes).to.be.eq(218910)
310 expect(jsonObj.items[0].attachments[0].url).to.contain('720.torrent')
311 }
312 })
57cfff78 313
cb0eda56 314 it('Should filter by account', async function () {
662fb3ab 315 {
cb0eda56 316 const json = await servers[0].feed.getJSON({ feed: 'videos', query: { accountId: rootAccountId }, ignoreCache: true })
c1bc8ee4 317 const jsonObj = JSON.parse(json)
662fb3ab 318 expect(jsonObj.items.length).to.be.equal(1)
a1587156 319 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
cb0eda56 320 expect(jsonObj.items[0].author.name).to.equal('Main root channel')
662fb3ab
C
321 }
322
323 {
cb0eda56 324 const json = await servers[0].feed.getJSON({ feed: 'videos', query: { accountId: userAccountId }, ignoreCache: true })
c1bc8ee4 325 const jsonObj = JSON.parse(json)
662fb3ab 326 expect(jsonObj.items.length).to.be.equal(1)
a1587156 327 expect(jsonObj.items[0].title).to.equal('user video')
cb0eda56 328 expect(jsonObj.items[0].author.name).to.equal('Main john channel')
662fb3ab 329 }
662fb3ab 330
cb0eda56
AG
331 for (const server of servers) {
332 {
333 const json = await server.feed.getJSON({ feed: 'videos', query: { accountName: 'root@' + servers[0].host }, ignoreCache: true })
334 const jsonObj = JSON.parse(json)
335 expect(jsonObj.items.length).to.be.equal(1)
336 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
337 }
338
339 {
340 const json = await server.feed.getJSON({ feed: 'videos', query: { accountName: 'john@' + servers[0].host }, ignoreCache: true })
341 const jsonObj = JSON.parse(json)
342 expect(jsonObj.items.length).to.be.equal(1)
343 expect(jsonObj.items[0].title).to.equal('user video')
344 }
345 }
346 })
662fb3ab 347
cb0eda56 348 it('Should filter by video channel', async function () {
662fb3ab 349 {
cb0eda56 350 const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true })
c1bc8ee4 351 const jsonObj = JSON.parse(json)
662fb3ab 352 expect(jsonObj.items.length).to.be.equal(1)
a1587156 353 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
cb0eda56 354 expect(jsonObj.items[0].author.name).to.equal('Main root channel')
662fb3ab
C
355 }
356
357 {
cb0eda56 358 const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: userChannelId }, ignoreCache: true })
c1bc8ee4 359 const jsonObj = JSON.parse(json)
662fb3ab 360 expect(jsonObj.items.length).to.be.equal(1)
a1587156 361 expect(jsonObj.items[0].title).to.equal('user video')
cb0eda56 362 expect(jsonObj.items[0].author.name).to.equal('Main john channel')
662fb3ab 363 }
97816649 364
cb0eda56
AG
365 for (const server of servers) {
366 {
367 const query = { videoChannelName: 'root_channel@' + servers[0].host }
368 const json = await server.feed.getJSON({ feed: 'videos', query, ignoreCache: true })
369 const jsonObj = JSON.parse(json)
370 expect(jsonObj.items.length).to.be.equal(1)
371 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
372 }
373
374 {
375 const query = { videoChannelName: 'john_channel@' + servers[0].host }
376 const json = await server.feed.getJSON({ feed: 'videos', query, ignoreCache: true })
377 const jsonObj = JSON.parse(json)
378 expect(jsonObj.items.length).to.be.equal(1)
379 expect(jsonObj.items[0].title).to.equal('user video')
380 }
381 }
382 })
97816649 383
cb0eda56
AG
384 it('Should correctly have videos feed with HLS only', async function () {
385 this.timeout(120000)
97816649 386
cb0eda56
AG
387 const json = await serverHLSOnly.feed.getJSON({ feed: 'videos', ignoreCache: true })
388 const jsonObj = JSON.parse(json)
389 expect(jsonObj.items.length).to.be.equal(1)
390 expect(jsonObj.items[0].attachments).to.exist
391 expect(jsonObj.items[0].attachments.length).to.be.eq(4)
7a5c3d77 392
cb0eda56
AG
393 for (let i = 0; i < 4; i++) {
394 expect(jsonObj.items[0].attachments[i].mime_type).to.be.eq('application/x-bittorrent')
395 expect(jsonObj.items[0].attachments[i].size_in_bytes).to.be.greaterThan(0)
396 expect(jsonObj.items[0].attachments[i].url).to.exist
7a5c3d77
C
397 }
398 })
7a5c3d77 399
cb0eda56
AG
400 it('Should not display waiting live videos', async function () {
401 const { uuid } = await servers[0].live.create({
402 fields: {
403 name: 'live',
404 privacy: VideoPrivacy.PUBLIC,
405 channelId: rootChannelId
406 }
407 })
408 liveId = uuid
7a5c3d77 409
cb0eda56
AG
410 const json = await servers[0].feed.getJSON({ feed: 'videos', ignoreCache: true })
411
412 const jsonObj = JSON.parse(json)
413 expect(jsonObj.items.length).to.be.equal(2)
414 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
415 expect(jsonObj.items[1].title).to.equal('user video')
416 })
7a5c3d77 417
cb0eda56
AG
418 it('Should display published live videos', async function () {
419 this.timeout(120000)
7a5c3d77 420
cb0eda56
AG
421 const ffmpeg = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveId, copyCodecs: true, fixtureName: 'video_short.mp4' })
422 await servers[0].live.waitUntilPublished({ videoId: liveId })
7a5c3d77 423
cb0eda56 424 const json = await servers[0].feed.getJSON({ feed: 'videos', ignoreCache: true })
7a5c3d77 425
cb0eda56
AG
426 const jsonObj = JSON.parse(json)
427 expect(jsonObj.items.length).to.be.equal(3)
428 expect(jsonObj.items[0].title).to.equal('live')
429 expect(jsonObj.items[1].title).to.equal('my super name for server 1')
430 expect(jsonObj.items[2].title).to.equal('user video')
7a5c3d77 431
cb0eda56
AG
432 await stopFfmpeg(ffmpeg)
433 })
5e1bd869 434
cb0eda56
AG
435 it('Should have the channel avatar as feed icon', async function () {
436 const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true })
5e1bd869 437
cb0eda56
AG
438 const jsonObj = JSON.parse(json)
439 const imageUrl = jsonObj.icon
440 expect(imageUrl).to.include('/lazy-static/avatars/')
441 await makeRawRequest({ url: imageUrl, expectedStatus: HttpStatusCode.OK_200 })
442 })
5e1bd869 443 })
fe3a55b0
C
444 })
445
446 describe('Video comments feed', function () {
68b6fd21
C
447
448 it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted videos', async function () {
fe3a55b0 449 for (const server of servers) {
41e74ec9 450 const json = await server.feed.getJSON({ feed: 'video-comments', ignoreCache: true })
fe3a55b0 451
c1bc8ee4 452 const jsonObj = JSON.parse(json)
fe3a55b0 453 expect(jsonObj.items.length).to.be.equal(2)
4393b255
C
454 expect(jsonObj.items[0].content_html).to.contain('<p>super comment 2</p>')
455 expect(jsonObj.items[1].content_html).to.contain('<p>super comment 1</p>')
fe3a55b0
C
456 }
457 })
1df8a4d7
C
458
459 it('Should not list comments from muted accounts or instances', async function () {
696d83fd
C
460 this.timeout(30000)
461
41e74ec9 462 const remoteHandle = 'root@' + servers[0].host
696d83fd 463
89d241a7 464 await servers[1].blocklist.addToServerBlocklist({ account: remoteHandle })
1df8a4d7
C
465
466 {
41e74ec9 467 const json = await servers[1].feed.getJSON({ feed: 'video-comments', ignoreCache: true })
c1bc8ee4 468 const jsonObj = JSON.parse(json)
1df8a4d7
C
469 expect(jsonObj.items.length).to.be.equal(0)
470 }
471
89d241a7 472 await servers[1].blocklist.removeFromServerBlocklist({ account: remoteHandle })
696d83fd
C
473
474 {
89d241a7 475 const videoUUID = (await servers[1].videos.quickUpload({ name: 'server 2' })).uuid
696d83fd 476 await waitJobs(servers)
89d241a7 477 await servers[0].comments.createThread({ videoId: videoUUID, text: 'super comment' })
696d83fd
C
478 await waitJobs(servers)
479
41e74ec9 480 const json = await servers[1].feed.getJSON({ feed: 'video-comments', ignoreCache: true })
c1bc8ee4 481 const jsonObj = JSON.parse(json)
696d83fd
C
482 expect(jsonObj.items.length).to.be.equal(3)
483 }
484
89d241a7 485 await servers[1].blocklist.addToMyBlocklist({ account: remoteHandle })
696d83fd
C
486
487 {
41e74ec9 488 const json = await servers[1].feed.getJSON({ feed: 'video-comments', ignoreCache: true })
c1bc8ee4 489 const jsonObj = JSON.parse(json)
696d83fd
C
490 expect(jsonObj.items.length).to.be.equal(2)
491 }
1df8a4d7 492 })
fe3a55b0
C
493 })
494
afff310e 495 describe('Video feed from my subscriptions', function () {
18490b07
C
496 let feeduserAccountId: number
497 let feeduserFeedToken: string
afff310e
RK
498
499 it('Should list no videos for a user with no videos and no subscriptions', async function () {
afff310e 500 const attr = { username: 'feeduser', password: 'password' }
89d241a7
C
501 await servers[0].users.create({ username: attr.username, password: attr.password })
502 const feeduserAccessToken = await servers[0].login.getAccessToken(attr)
afff310e
RK
503
504 {
89d241a7 505 const user = await servers[0].users.getMyInfo({ token: feeduserAccessToken })
afff310e
RK
506 feeduserAccountId = user.account.id
507 }
508
509 {
89d241a7 510 const token = await servers[0].users.getMyScopedTokens({ token: feeduserAccessToken })
afff310e
RK
511 feeduserFeedToken = token.feedToken
512 }
513
514 {
692ae8c3 515 const body = await servers[0].videos.listMySubscriptionVideos({ token: feeduserAccessToken })
2c27e704 516 expect(body.total).to.equal(0)
afff310e 517
c1bc8ee4 518 const query = { accountId: feeduserAccountId, token: feeduserFeedToken }
41e74ec9 519 const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true })
c1bc8ee4 520 const jsonObj = JSON.parse(json)
afff310e
RK
521 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
522 }
523 })
524
18490b07 525 it('Should fail with an invalid token', async function () {
c1bc8ee4 526 const query = { accountId: feeduserAccountId, token: 'toto' }
41e74ec9 527 await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403, ignoreCache: true })
18490b07
C
528 })
529
530 it('Should fail with a token of another user', async function () {
c1bc8ee4 531 const query = { accountId: feeduserAccountId, token: userFeedToken }
41e74ec9 532 await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403, ignoreCache: true })
18490b07
C
533 })
534
afff310e 535 it('Should list no videos for a user with videos but no subscriptions', async function () {
692ae8c3 536 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
2c27e704 537 expect(body.total).to.equal(0)
afff310e 538
c1bc8ee4 539 const query = { accountId: userAccountId, token: userFeedToken }
41e74ec9 540 const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true })
c1bc8ee4 541 const jsonObj = JSON.parse(json)
18490b07 542 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
afff310e
RK
543 })
544
545 it('Should list self videos for a user with a subscription to themselves', async function () {
546 this.timeout(30000)
547
41e74ec9 548 await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'john_channel@' + servers[0].host })
afff310e
RK
549 await waitJobs(servers)
550
551 {
692ae8c3 552 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
2c27e704
C
553 expect(body.total).to.equal(1)
554 expect(body.data[0].name).to.equal('user video')
afff310e 555
41e74ec9
C
556 const query = { accountId: userAccountId, token: userFeedToken }
557 const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true })
c1bc8ee4 558 const jsonObj = JSON.parse(json)
afff310e
RK
559 expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's
560 }
561 })
562
563 it('Should list videos of a user\'s subscription', async function () {
564 this.timeout(30000)
565
41e74ec9 566 await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'root_channel@' + servers[0].host })
afff310e
RK
567 await waitJobs(servers)
568
569 {
692ae8c3 570 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
7e0f50d6 571 expect(body.total).to.equal(2, 'there should be 2 videos part of the subscription')
afff310e 572
41e74ec9
C
573 const query = { accountId: userAccountId, token: userFeedToken }
574 const json = await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true })
c1bc8ee4 575 const jsonObj = JSON.parse(json)
afff310e
RK
576 expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's
577 }
578 })
579
18490b07 580 it('Should renew the token, and so have an invalid old token', async function () {
89d241a7 581 await servers[0].users.renewMyScopedTokens({ token: userAccessToken })
18490b07 582
41e74ec9
C
583 const query = { accountId: userAccountId, token: userFeedToken }
584 await servers[0].feed.getJSON({ feed: 'subscriptions', query, expectedStatus: HttpStatusCode.FORBIDDEN_403, ignoreCache: true })
18490b07
C
585 })
586
587 it('Should succeed with the new token', async function () {
89d241a7 588 const token = await servers[0].users.getMyScopedTokens({ token: userAccessToken })
18490b07
C
589 userFeedToken = token.feedToken
590
41e74ec9
C
591 const query = { accountId: userAccountId, token: userFeedToken }
592 await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true })
18490b07
C
593 })
594
afff310e
RK
595 })
596
ed14d1eb
C
597 describe('Cache', function () {
598 const uuids: string[] = []
599
600 function doPodcastRequest () {
601 return makeGetRequest({
602 url: servers[0].url,
603 path: '/feeds/podcast/videos.xml',
604 query: { videoChannelId: servers[0].store.channel.id },
605 accept: 'application/xml',
606 expectedStatus: HttpStatusCode.OK_200
607 })
608 }
609
610 function doVideosRequest (query: { [id: string]: string } = {}) {
611 return makeGetRequest({
612 url: servers[0].url,
613 path: '/feeds/videos.xml',
614 query,
615 accept: 'application/xml',
616 expectedStatus: HttpStatusCode.OK_200
617 })
618 }
619
620 before(async function () {
621 {
622 const { uuid } = await servers[0].videos.quickUpload({ name: 'cache 1' })
623 uuids.push(uuid)
624 }
625
626 {
627 const { uuid } = await servers[0].videos.quickUpload({ name: 'cache 2' })
628 uuids.push(uuid)
629 }
630 })
631
632 it('Should serve the videos endpoint as a cached request', async function () {
633 await doVideosRequest()
634
635 const res = await doVideosRequest()
636
637 expect(res.headers['x-api-cache-cached']).to.equal('true')
638 })
639
640 it('Should not serve the videos endpoint as a cached request', async function () {
641 const res = await doVideosRequest({ v: '186' })
642
643 expect(res.headers['x-api-cache-cached']).to.not.exist
644 })
645
646 it('Should invalidate the podcast feed cache after video deletion', async function () {
647 await doPodcastRequest()
648
649 {
650 const res = await doPodcastRequest()
651 expect(res.headers['x-api-cache-cached']).to.exist
652 }
653
654 await servers[0].videos.remove({ id: uuids[0] })
655
656 {
657 const res = await doPodcastRequest()
658 expect(res.headers['x-api-cache-cached']).to.not.exist
659 }
660 })
661
662 it('Should invalidate the podcast feed cache after video deletion, even after server restart', async function () {
663 this.timeout(120000)
664
665 await doPodcastRequest()
666
667 {
668 const res = await doPodcastRequest()
669 expect(res.headers['x-api-cache-cached']).to.exist
670 }
671
672 await servers[0].kill()
673 await servers[0].run()
674
675 await servers[0].videos.remove({ id: uuids[1] })
676
677 const res = await doPodcastRequest()
678 expect(res.headers['x-api-cache-cached']).to.not.exist
679 })
680
681 })
682
7c3b7976 683 after(async function () {
cb0eda56
AG
684 await servers[0].plugins.uninstall({ npmName: 'peertube-plugin-test-podcast-custom-tags' })
685
97816649 686 await cleanupTests([ ...servers, serverHLSOnly ])
fe3a55b0
C
687 })
688})