diff options
Diffstat (limited to 'server/tests/feeds')
-rw-r--r-- | server/tests/feeds/feeds.ts | 695 | ||||
-rw-r--r-- | server/tests/feeds/index.ts | 1 |
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 | |||
3 | import * as chai from 'chai' | ||
4 | import { XMLParser, XMLValidator } from 'fast-xml-parser' | ||
5 | import { HttpStatusCode, VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
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 | |||
22 | chai.use(require('chai-xml')) | ||
23 | chai.use(require('chai-json-schema')) | ||
24 | chai.config.includeStack = true | ||
25 | |||
26 | const expect = chai.expect | ||
27 | |||
28 | describe('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 @@ | |||
1 | import './feeds' | ||