1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { omit } from 'lodash'
6 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
7 import { Account, HTMLServerConfig, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models'
11 flushAndRunMultipleServers,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
20 } from '../../shared/extra-utils'
22 const expect = chai.expect
24 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
25 expect(html).to.contain('<title>' + title + '</title>')
26 expect(html).to.contain('<meta name="description" content="' + description + '" />')
27 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
29 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
30 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
33 describe('Test a client controllers', function () {
34 let servers: ServerInfo[] = []
37 const videoName = 'my super name for server 1'
38 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
39 const videoDescriptionPlainText = 'my super description for server 1'
41 const playlistName = 'super playlist name'
42 const playlistDescription = 'super playlist description'
43 let playlist: VideoPlaylistCreateResult
45 const channelDescription = 'my super channel description'
47 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
48 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
50 let videoIds: (string | number)[] = []
51 let playlistIds: (string | number)[] = []
53 before(async function () {
56 servers = await flushAndRunMultipleServers(2)
58 await setAccessTokensToServers(servers)
60 await doubleFollow(servers[0], servers[1])
62 await setDefaultVideoChannel(servers)
64 await servers[0].channelsCommand.update({
65 channelName: servers[0].videoChannel.name,
66 attributes: { description: channelDescription }
71 const videoAttributes = { name: videoName, description: videoDescription }
72 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
74 const resVideosRequest = await getVideosList(servers[0].url)
75 const videos = resVideosRequest.body.data
76 expect(videos.length).to.equal(1)
78 const video = videos[0]
79 servers[0].video = video
80 videoIds = [ video.id, video.uuid, video.shortUUID ]
85 displayName: playlistName,
86 description: playlistDescription,
87 privacy: VideoPlaylistPrivacy.PUBLIC,
88 videoChannelId: servers[0].videoChannel.id
91 playlist = await servers[0].playlistsCommand.create({ attributes })
92 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
94 await servers[0].playlistsCommand.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: video.id } })
98 await servers[0].usersCommand.updateMe({ description: 'my account description' })
100 account = await servers[0].accountsCommand.get({ accountName: `${servers[0].user.username}@${servers[0].host}` })
102 await waitJobs(servers)
105 describe('oEmbed', function () {
107 it('Should have valid oEmbed discovery tags for videos', async function () {
108 for (const basePath of watchVideoBasePaths) {
109 for (const id of videoIds) {
110 const res = await makeGetRequest({
114 statusCodeExpected: HttpStatusCode.OK_200
117 const port = servers[0].port
119 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
120 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
121 `title="${servers[0].video.name}" />`
123 expect(res.text).to.contain(expectedLink)
128 it('Should have valid oEmbed discovery tags for a playlist', async function () {
129 for (const basePath of watchPlaylistBasePaths) {
130 for (const id of playlistIds) {
131 const res = await makeGetRequest({
135 statusCodeExpected: HttpStatusCode.OK_200
138 const port = servers[0].port
140 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
141 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
142 `title="${playlistName}" />`
144 expect(res.text).to.contain(expectedLink)
150 describe('Open Graph', function () {
152 async function accountPageTest (path: string) {
153 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
154 const text = res.text
156 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
157 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
158 expect(text).to.contain('<meta property="og:type" content="website" />')
159 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
162 async function channelPageTest (path: string) {
163 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
164 const text = res.text
166 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
167 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
168 expect(text).to.contain('<meta property="og:type" content="website" />')
169 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
172 async function watchVideoPageTest (path: string) {
173 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
174 const text = res.text
176 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
177 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
178 expect(text).to.contain('<meta property="og:type" content="video" />')
179 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
182 async function watchPlaylistPageTest (path: string) {
183 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
184 const text = res.text
186 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
187 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
188 expect(text).to.contain('<meta property="og:type" content="video" />')
189 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.uuid}" />`)
192 it('Should have valid Open Graph tags on the account page', async function () {
193 await accountPageTest('/accounts/' + servers[0].user.username)
194 await accountPageTest('/a/' + servers[0].user.username)
195 await accountPageTest('/@' + servers[0].user.username)
198 it('Should have valid Open Graph tags on the channel page', async function () {
199 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
200 await channelPageTest('/c/' + servers[0].videoChannel.name)
201 await channelPageTest('/@' + servers[0].videoChannel.name)
204 it('Should have valid Open Graph tags on the watch page', async function () {
205 for (const path of watchVideoBasePaths) {
206 for (const id of videoIds) {
207 await watchVideoPageTest(path + id)
212 it('Should have valid Open Graph tags on the watch playlist page', async function () {
213 for (const path of watchPlaylistBasePaths) {
214 for (const id of playlistIds) {
215 await watchPlaylistPageTest(path + id)
221 describe('Twitter card', async function () {
223 describe('Not whitelisted', function () {
225 async function accountPageTest (path: string) {
226 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
227 const text = res.text
229 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
230 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
231 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
232 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
235 async function channelPageTest (path: string) {
236 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
237 const text = res.text
239 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
240 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
241 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
242 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
245 async function watchVideoPageTest (path: string) {
246 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
247 const text = res.text
249 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
250 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
251 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
252 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
255 async function watchPlaylistPageTest (path: string) {
256 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
257 const text = res.text
259 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
260 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
261 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
262 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
265 it('Should have valid twitter card on the watch video page', async function () {
266 for (const path of watchVideoBasePaths) {
267 for (const id of videoIds) {
268 await watchVideoPageTest(path + id)
273 it('Should have valid twitter card on the watch playlist page', async function () {
274 for (const path of watchPlaylistBasePaths) {
275 for (const id of playlistIds) {
276 await watchPlaylistPageTest(path + id)
281 it('Should have valid twitter card on the account page', async function () {
282 await accountPageTest('/accounts/' + account.name)
283 await accountPageTest('/a/' + account.name)
284 await accountPageTest('/@' + account.name)
287 it('Should have valid twitter card on the channel page', async function () {
288 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
289 await channelPageTest('/c/' + servers[0].videoChannel.name)
290 await channelPageTest('/@' + servers[0].videoChannel.name)
294 describe('Whitelisted', function () {
296 before(async function () {
297 const config = await servers[0].configCommand.getCustomConfig()
298 config.services.twitter = {
303 await servers[0].configCommand.updateCustomConfig({ newCustomConfig: config })
306 async function accountPageTest (path: string) {
307 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
308 const text = res.text
310 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
311 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
314 async function channelPageTest (path: string) {
315 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
316 const text = res.text
318 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
319 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
322 async function watchVideoPageTest (path: string) {
323 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
324 const text = res.text
326 expect(text).to.contain('<meta property="twitter:card" content="player" />')
327 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
330 async function watchPlaylistPageTest (path: string) {
331 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
332 const text = res.text
334 expect(text).to.contain('<meta property="twitter:card" content="player" />')
335 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
338 it('Should have valid twitter card on the watch video page', async function () {
339 for (const path of watchVideoBasePaths) {
340 for (const id of videoIds) {
341 await watchVideoPageTest(path + id)
346 it('Should have valid twitter card on the watch playlist page', async function () {
347 for (const path of watchPlaylistBasePaths) {
348 for (const id of playlistIds) {
349 await watchPlaylistPageTest(path + id)
354 it('Should have valid twitter card on the account page', async function () {
355 await accountPageTest('/accounts/' + account.name)
356 await accountPageTest('/a/' + account.name)
357 await accountPageTest('/@' + account.name)
360 it('Should have valid twitter card on the channel page', async function () {
361 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
362 await channelPageTest('/c/' + servers[0].videoChannel.name)
363 await channelPageTest('/@' + servers[0].videoChannel.name)
368 describe('Index HTML', function () {
370 it('Should have valid index html tags (title, description...)', async function () {
371 const config = await servers[0].configCommand.getConfig()
372 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
374 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
375 checkIndexTags(res.text, 'PeerTube', description, '', config)
378 it('Should update the customized configuration and have the correct index html tags', async function () {
379 await servers[0].configCommand.updateCustomSubConfig({
382 name: 'PeerTube updated',
383 shortDescription: 'my short description',
384 description: 'my super description',
385 terms: 'my super terms',
386 defaultNSFWPolicy: 'blur',
387 defaultClientRoute: '/videos/recently-added',
389 javascript: 'alert("coucou")',
390 css: 'body { background-color: red; }'
396 const config = await servers[0].configCommand.getConfig()
397 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
399 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
402 it('Should have valid index html updated tags (title, description...)', async function () {
403 const config = await servers[0].configCommand.getConfig()
404 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
406 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
409 it('Should use the original video URL for the canonical tag', async function () {
410 for (const basePath of watchVideoBasePaths) {
411 for (const id of videoIds) {
412 const res = await makeHTMLRequest(servers[1].url, basePath + id)
413 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
418 it('Should use the original account URL for the canonical tag', async function () {
419 const accountURLtest = res => {
420 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
423 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
424 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
425 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
428 it('Should use the original channel URL for the canonical tag', async function () {
429 const channelURLtests = res => {
430 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
433 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
434 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
435 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
438 it('Should use the original playlist URL for the canonical tag', async function () {
439 for (const basePath of watchPlaylistBasePaths) {
440 for (const id of playlistIds) {
441 const res = await makeHTMLRequest(servers[1].url, basePath + id)
442 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
448 describe('Embed HTML', function () {
450 it('Should have the correct embed html tags', async function () {
451 const config = await servers[0].configCommand.getConfig()
452 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
454 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
458 after(async function () {
459 await cleanupTests(servers)