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