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,
21 } from '../../shared/extra-utils'
23 const expect = chai.expect
25 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
26 expect(html).to.contain('<title>' + title + '</title>')
27 expect(html).to.contain('<meta name="description" content="' + description + '" />')
28 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
30 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
31 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
34 describe('Test a client controllers', function () {
35 let servers: ServerInfo[] = []
38 const videoName = 'my super name for server 1'
39 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
40 const videoDescriptionPlainText = 'my super description for server 1'
42 const playlistName = 'super playlist name'
43 const playlistDescription = 'super playlist description'
44 let playlist: VideoPlaylistCreateResult
46 const channelDescription = 'my super channel description'
48 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
49 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
51 let videoIds: (string | number)[] = []
52 let playlistIds: (string | number)[] = []
54 before(async function () {
57 servers = await flushAndRunMultipleServers(2)
59 await setAccessTokensToServers(servers)
61 await doubleFollow(servers[0], servers[1])
63 await setDefaultVideoChannel(servers)
65 await servers[0].channelsCommand.update({
66 channelName: servers[0].videoChannel.name,
67 attributes: { description: channelDescription }
72 const videoAttributes = { name: videoName, description: videoDescription }
73 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
75 const resVideosRequest = await getVideosList(servers[0].url)
76 const videos = resVideosRequest.body.data
77 expect(videos.length).to.equal(1)
79 const video = videos[0]
80 servers[0].video = video
81 videoIds = [ video.id, video.uuid, video.shortUUID ]
86 displayName: playlistName,
87 description: playlistDescription,
88 privacy: VideoPlaylistPrivacy.PUBLIC,
89 videoChannelId: servers[0].videoChannel.id
92 playlist = await servers[0].playlistsCommand.create({ attributes })
93 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
95 await servers[0].playlistsCommand.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: video.id } })
99 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
101 account = await servers[0].accountsCommand.get({ accountName: `${servers[0].user.username}@${servers[0].host}` })
103 await waitJobs(servers)
106 describe('oEmbed', function () {
108 it('Should have valid oEmbed discovery tags for videos', async function () {
109 for (const basePath of watchVideoBasePaths) {
110 for (const id of videoIds) {
111 const res = await makeGetRequest({
115 statusCodeExpected: HttpStatusCode.OK_200
118 const port = servers[0].port
120 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
121 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
122 `title="${servers[0].video.name}" />`
124 expect(res.text).to.contain(expectedLink)
129 it('Should have valid oEmbed discovery tags for a playlist', async function () {
130 for (const basePath of watchPlaylistBasePaths) {
131 for (const id of playlistIds) {
132 const res = await makeGetRequest({
136 statusCodeExpected: HttpStatusCode.OK_200
139 const port = servers[0].port
141 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
142 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
143 `title="${playlistName}" />`
145 expect(res.text).to.contain(expectedLink)
151 describe('Open Graph', function () {
153 async function accountPageTest (path: string) {
154 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
155 const text = res.text
157 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
158 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
159 expect(text).to.contain('<meta property="og:type" content="website" />')
160 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
163 async function channelPageTest (path: string) {
164 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
165 const text = res.text
167 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
168 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
169 expect(text).to.contain('<meta property="og:type" content="website" />')
170 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
173 async function watchVideoPageTest (path: string) {
174 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
175 const text = res.text
177 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
178 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
179 expect(text).to.contain('<meta property="og:type" content="video" />')
180 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
183 async function watchPlaylistPageTest (path: string) {
184 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
185 const text = res.text
187 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
188 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
189 expect(text).to.contain('<meta property="og:type" content="video" />')
190 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.uuid}" />`)
193 it('Should have valid Open Graph tags on the account page', async function () {
194 await accountPageTest('/accounts/' + servers[0].user.username)
195 await accountPageTest('/a/' + servers[0].user.username)
196 await accountPageTest('/@' + servers[0].user.username)
199 it('Should have valid Open Graph tags on the channel page', async function () {
200 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
201 await channelPageTest('/c/' + servers[0].videoChannel.name)
202 await channelPageTest('/@' + servers[0].videoChannel.name)
205 it('Should have valid Open Graph tags on the watch page', async function () {
206 for (const path of watchVideoBasePaths) {
207 for (const id of videoIds) {
208 await watchVideoPageTest(path + id)
213 it('Should have valid Open Graph tags on the watch playlist page', async function () {
214 for (const path of watchPlaylistBasePaths) {
215 for (const id of playlistIds) {
216 await watchPlaylistPageTest(path + id)
222 describe('Twitter card', async function () {
224 describe('Not whitelisted', function () {
226 async function accountPageTest (path: string) {
227 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
228 const text = res.text
230 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
231 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
232 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
233 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
236 async function channelPageTest (path: string) {
237 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
238 const text = res.text
240 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
241 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
242 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
243 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
246 async function watchVideoPageTest (path: string) {
247 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
248 const text = res.text
250 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
251 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
252 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
253 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
256 async function watchPlaylistPageTest (path: string) {
257 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
258 const text = res.text
260 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
261 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
262 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
263 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
266 it('Should have valid twitter card on the watch video page', async function () {
267 for (const path of watchVideoBasePaths) {
268 for (const id of videoIds) {
269 await watchVideoPageTest(path + id)
274 it('Should have valid twitter card on the watch playlist page', async function () {
275 for (const path of watchPlaylistBasePaths) {
276 for (const id of playlistIds) {
277 await watchPlaylistPageTest(path + id)
282 it('Should have valid twitter card on the account page', async function () {
283 await accountPageTest('/accounts/' + account.name)
284 await accountPageTest('/a/' + account.name)
285 await accountPageTest('/@' + account.name)
288 it('Should have valid twitter card on the channel page', async function () {
289 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
290 await channelPageTest('/c/' + servers[0].videoChannel.name)
291 await channelPageTest('/@' + servers[0].videoChannel.name)
295 describe('Whitelisted', function () {
297 before(async function () {
298 const config = await servers[0].configCommand.getCustomConfig()
299 config.services.twitter = {
304 await servers[0].configCommand.updateCustomConfig({ newCustomConfig: config })
307 async function accountPageTest (path: string) {
308 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
309 const text = res.text
311 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
312 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
315 async function channelPageTest (path: string) {
316 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
317 const text = res.text
319 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
320 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
323 async function watchVideoPageTest (path: string) {
324 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
325 const text = res.text
327 expect(text).to.contain('<meta property="twitter:card" content="player" />')
328 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
331 async function watchPlaylistPageTest (path: string) {
332 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
333 const text = res.text
335 expect(text).to.contain('<meta property="twitter:card" content="player" />')
336 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
339 it('Should have valid twitter card on the watch video page', async function () {
340 for (const path of watchVideoBasePaths) {
341 for (const id of videoIds) {
342 await watchVideoPageTest(path + id)
347 it('Should have valid twitter card on the watch playlist page', async function () {
348 for (const path of watchPlaylistBasePaths) {
349 for (const id of playlistIds) {
350 await watchPlaylistPageTest(path + id)
355 it('Should have valid twitter card on the account page', async function () {
356 await accountPageTest('/accounts/' + account.name)
357 await accountPageTest('/a/' + account.name)
358 await accountPageTest('/@' + account.name)
361 it('Should have valid twitter card on the channel page', async function () {
362 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
363 await channelPageTest('/c/' + servers[0].videoChannel.name)
364 await channelPageTest('/@' + servers[0].videoChannel.name)
369 describe('Index HTML', function () {
371 it('Should have valid index html tags (title, description...)', async function () {
372 const config = await servers[0].configCommand.getConfig()
373 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
375 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
376 checkIndexTags(res.text, 'PeerTube', description, '', config)
379 it('Should update the customized configuration and have the correct index html tags', async function () {
380 await servers[0].configCommand.updateCustomSubConfig({
383 name: 'PeerTube updated',
384 shortDescription: 'my short description',
385 description: 'my super description',
386 terms: 'my super terms',
387 defaultNSFWPolicy: 'blur',
388 defaultClientRoute: '/videos/recently-added',
390 javascript: 'alert("coucou")',
391 css: 'body { background-color: red; }'
397 const config = await servers[0].configCommand.getConfig()
398 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
400 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
403 it('Should have valid index html updated tags (title, description...)', async function () {
404 const config = await servers[0].configCommand.getConfig()
405 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
407 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
410 it('Should use the original video URL for the canonical tag', async function () {
411 for (const basePath of watchVideoBasePaths) {
412 for (const id of videoIds) {
413 const res = await makeHTMLRequest(servers[1].url, basePath + id)
414 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
419 it('Should use the original account URL for the canonical tag', async function () {
420 const accountURLtest = res => {
421 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
424 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
425 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
426 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
429 it('Should use the original channel URL for the canonical tag', async function () {
430 const channelURLtests = res => {
431 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
434 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
435 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
436 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
439 it('Should use the original playlist URL for the canonical tag', async function () {
440 for (const basePath of watchPlaylistBasePaths) {
441 for (const id of playlistIds) {
442 const res = await makeHTMLRequest(servers[1].url, basePath + id)
443 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
449 describe('Embed HTML', function () {
451 it('Should have the correct embed html tags', async function () {
452 const config = await servers[0].configCommand.getConfig()
453 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
455 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
459 after(async function () {
460 await cleanupTests(servers)