はじまりの大地

このコミットが含まれているのは:
2024-07-15 09:14:04 +09:00
コミット 6632905f32
3501個のファイルの変更1439465行の追加0行の削除
+342
ファイルの表示
@@ -0,0 +1,342 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { SQLCommand } from '@tests/shared/sql-command.js'
import { wait } from '@peertube/peertube-core-utils'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test AP cleaner', function () {
let servers: PeerTubeServer[] = []
const sqlCommands: SQLCommand[] = []
let videoUUID1: string
let videoUUID2: string
let videoUUID3: string
let videoUUIDs: string[]
before(async function () {
this.timeout(240000)
const config = {
federation: {
videos: { cleanup_remote_interactions: true }
}
}
servers = await createMultipleServers(3, config)
// Get the access tokens
await setAccessTokensToServers(servers)
await Promise.all([
doubleFollow(servers[0], servers[1]),
doubleFollow(servers[1], servers[2]),
doubleFollow(servers[0], servers[2])
])
// Update 1 local share, check 6 shares
// Create 1 comment per video
// Update 1 remote URL and 1 local URL on
videoUUID1 = (await servers[0].videos.quickUpload({ name: 'server 1' })).uuid
videoUUID2 = (await servers[1].videos.quickUpload({ name: 'server 2' })).uuid
videoUUID3 = (await servers[2].videos.quickUpload({ name: 'server 3' })).uuid
videoUUIDs = [ videoUUID1, videoUUID2, videoUUID3 ]
await waitJobs(servers)
for (const server of servers) {
for (const uuid of videoUUIDs) {
await server.videos.rate({ id: uuid, rating: 'like' })
await server.comments.createThread({ videoId: uuid, text: 'comment' })
}
sqlCommands.push(new SQLCommand(server))
}
await waitJobs(servers)
})
it('Should have the correct likes', async function () {
for (const server of servers) {
for (const uuid of videoUUIDs) {
const video = await server.videos.get({ id: uuid })
expect(video.likes).to.equal(3)
expect(video.dislikes).to.equal(0)
}
}
})
it('Should destroy server 3 internal likes and correctly clean them', async function () {
this.timeout(20000)
await sqlCommands[2].deleteAll('accountVideoRate')
for (const uuid of videoUUIDs) {
await sqlCommands[2].setVideoField(uuid, 'likes', '0')
}
await wait(5000)
await waitJobs(servers)
// Updated rates of my video
{
const video = await servers[0].videos.get({ id: videoUUID1 })
expect(video.likes).to.equal(2)
expect(video.dislikes).to.equal(0)
}
// Did not update rates of a remote video
{
const video = await servers[0].videos.get({ id: videoUUID2 })
expect(video.likes).to.equal(3)
expect(video.dislikes).to.equal(0)
}
})
it('Should update rates to dislikes', async function () {
this.timeout(20000)
for (const server of servers) {
for (const uuid of videoUUIDs) {
await server.videos.rate({ id: uuid, rating: 'dislike' })
}
}
await waitJobs(servers)
for (const server of servers) {
for (const uuid of videoUUIDs) {
const video = await server.videos.get({ id: uuid })
expect(video.likes).to.equal(0)
expect(video.dislikes).to.equal(3)
}
}
})
it('Should destroy server 3 internal dislikes and correctly clean them', async function () {
this.timeout(20000)
await sqlCommands[2].deleteAll('accountVideoRate')
for (const uuid of videoUUIDs) {
await sqlCommands[2].setVideoField(uuid, 'dislikes', '0')
}
await wait(5000)
await waitJobs(servers)
// Updated rates of my video
{
const video = await servers[0].videos.get({ id: videoUUID1 })
expect(video.likes).to.equal(0)
expect(video.dislikes).to.equal(2)
}
// Did not update rates of a remote video
{
const video = await servers[0].videos.get({ id: videoUUID2 })
expect(video.likes).to.equal(0)
expect(video.dislikes).to.equal(3)
}
})
it('Should destroy server 3 internal shares and correctly clean them', async function () {
this.timeout(20000)
const preCount = await sqlCommands[0].getVideoShareCount()
expect(preCount).to.equal(6)
await sqlCommands[2].deleteAll('videoShare')
await wait(5000)
await waitJobs(servers)
// Still 6 because we don't have remote shares on local videos
const postCount = await sqlCommands[0].getVideoShareCount()
expect(postCount).to.equal(6)
})
it('Should destroy server 3 internal comments and correctly clean them', async function () {
this.timeout(20000)
{
const { total } = await servers[0].comments.listThreads({ videoId: videoUUID1 })
expect(total).to.equal(3)
}
await sqlCommands[2].deleteAll('videoComment')
await wait(5000)
await waitJobs(servers)
{
const { total } = await servers[0].comments.listThreads({ videoId: videoUUID1 })
expect(total).to.equal(2)
}
})
it('Should correctly update rate URLs', async function () {
this.timeout(30000)
async function check (like: string, ofServerUrl: string, urlSuffix: string, remote: 'true' | 'false') {
const query = `SELECT "videoId", "accountVideoRate".url FROM "accountVideoRate" ` +
`INNER JOIN video ON "accountVideoRate"."videoId" = video.id AND remote IS ${remote} WHERE "accountVideoRate"."url" LIKE '${like}'`
const res = await sqlCommands[0].selectQuery<{ url: string }>(query)
for (const rate of res) {
const matcher = new RegExp(`^${ofServerUrl}/accounts/root/dislikes/\\d+${urlSuffix}$`)
expect(rate.url).to.match(matcher)
}
}
async function checkLocal () {
const startsWith = 'http://' + servers[0].host + '%'
// On local videos
await check(startsWith, servers[0].url, '', 'false')
// On remote videos
await check(startsWith, servers[0].url, '', 'true')
}
async function checkRemote (suffix: string) {
const startsWith = 'http://' + servers[1].host + '%'
// On local videos
await check(startsWith, servers[1].url, suffix, 'false')
// On remote videos, we should not update URLs so no suffix
await check(startsWith, servers[1].url, '', 'true')
}
await checkLocal()
await checkRemote('')
{
const query = `UPDATE "accountVideoRate" SET url = url || 'stan'`
await sqlCommands[1].updateQuery(query)
await wait(5000)
await waitJobs(servers)
}
await checkLocal()
await checkRemote('stan')
})
it('Should correctly update comment URLs', async function () {
this.timeout(30000)
async function check (like: string, ofServerUrl: string, urlSuffix: string, remote: 'true' | 'false') {
const query = `SELECT "videoId", "videoComment".url, uuid as "videoUUID" FROM "videoComment" ` +
`INNER JOIN video ON "videoComment"."videoId" = video.id AND remote IS ${remote} WHERE "videoComment"."url" LIKE '${like}'`
const res = await sqlCommands[0].selectQuery<{ url: string, videoUUID: string }>(query)
for (const comment of res) {
const matcher = new RegExp(`${ofServerUrl}/videos/watch/${comment.videoUUID}/comments/\\d+${urlSuffix}`)
expect(comment.url).to.match(matcher)
}
}
async function checkLocal () {
const startsWith = 'http://' + servers[0].host + '%'
// On local videos
await check(startsWith, servers[0].url, '', 'false')
// On remote videos
await check(startsWith, servers[0].url, '', 'true')
}
async function checkRemote (suffix: string) {
const startsWith = 'http://' + servers[1].host + '%'
// On local videos
await check(startsWith, servers[1].url, suffix, 'false')
// On remote videos, we should not update URLs so no suffix
await check(startsWith, servers[1].url, '', 'true')
}
{
const query = `UPDATE "videoComment" SET url = url || 'kyle'`
await sqlCommands[1].updateQuery(query)
await wait(5000)
await waitJobs(servers)
}
await checkLocal()
await checkRemote('kyle')
})
it('Should remove unavailable remote resources', async function () {
this.timeout(240000)
async function expectNotDeleted () {
{
const video = await servers[0].videos.get({ id: uuid })
expect(video.likes).to.equal(3)
expect(video.dislikes).to.equal(0)
}
{
const { total } = await servers[0].comments.listThreads({ videoId: uuid })
expect(total).to.equal(3)
}
}
async function expectDeleted () {
{
const video = await servers[0].videos.get({ id: uuid })
expect(video.likes).to.equal(2)
expect(video.dislikes).to.equal(0)
}
{
const { total } = await servers[0].comments.listThreads({ videoId: uuid })
expect(total).to.equal(2)
}
}
const uuid = (await servers[0].videos.quickUpload({ name: 'server 1 video 2' })).uuid
await waitJobs(servers)
for (const server of servers) {
await server.videos.rate({ id: uuid, rating: 'like' })
await server.comments.createThread({ videoId: uuid, text: 'comment' })
}
await waitJobs(servers)
await expectNotDeleted()
await servers[1].kill()
await wait(5000)
await expectNotDeleted()
let continueWhile = true
do {
try {
await expectDeleted()
continueWhile = false
} catch {
}
} while (continueWhile)
})
after(async function () {
for (const sql of sqlCommands) {
await sql.cleanup()
}
await cleanupTests(servers)
})
})
+136
ファイルの表示
@@ -0,0 +1,136 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { processViewersStats } from '@tests/shared/views.js'
import { HttpStatusCode, VideoPlaylistPrivacy, WatchActionObject } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
makeActivityPubGetRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test activitypub', function () {
let servers: PeerTubeServer[] = []
let video: { id: number, uuid: string, shortUUID: string }
let playlist: { id: number, uuid: string, shortUUID: string }
async function testAccount (path: string) {
const res = await makeActivityPubGetRequest(servers[0].url, path)
const object = res.body
expect(object.type).to.equal('Person')
expect(object.id).to.equal(servers[0].url + '/accounts/root')
expect(object.name).to.equal('root')
expect(object.preferredUsername).to.equal('root')
}
async function testChannel (path: string) {
const res = await makeActivityPubGetRequest(servers[0].url, path)
const object = res.body
expect(object.type).to.equal('Group')
expect(object.id).to.equal(servers[0].url + '/video-channels/root_channel')
expect(object.name).to.equal('Main root channel')
expect(object.preferredUsername).to.equal('root_channel')
}
async function testVideo (path: string) {
const res = await makeActivityPubGetRequest(servers[0].url, path)
const object = res.body
expect(object.type).to.equal('Video')
expect(object.id).to.equal(servers[0].url + '/videos/watch/' + video.uuid)
expect(object.name).to.equal('video')
}
async function testPlaylist (path: string) {
const res = await makeActivityPubGetRequest(servers[0].url, path)
const object = res.body
expect(object.type).to.equal('Playlist')
expect(object.id).to.equal(servers[0].url + '/video-playlists/' + playlist.uuid)
expect(object.name).to.equal('playlist')
}
before(async function () {
this.timeout(30000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
{
video = await servers[0].videos.quickUpload({ name: 'video' })
}
{
const attributes = { displayName: 'playlist', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[0].store.channel.id }
playlist = await servers[0].playlists.create({ attributes })
}
await doubleFollow(servers[0], servers[1])
})
it('Should return the account object', async function () {
await testAccount('/accounts/root')
await testAccount('/a/root')
})
it('Should return the channel object', async function () {
await testChannel('/video-channels/root_channel')
await testChannel('/c/root_channel')
})
it('Should return the video object', async function () {
await testVideo('/videos/watch/' + video.id)
await testVideo('/videos/watch/' + video.uuid)
await testVideo('/videos/watch/' + video.shortUUID)
await testVideo('/w/' + video.id)
await testVideo('/w/' + video.uuid)
await testVideo('/w/' + video.shortUUID)
})
it('Should return the playlist object', async function () {
await testPlaylist('/video-playlists/' + playlist.id)
await testPlaylist('/video-playlists/' + playlist.uuid)
await testPlaylist('/video-playlists/' + playlist.shortUUID)
await testPlaylist('/w/p/' + playlist.id)
await testPlaylist('/w/p/' + playlist.uuid)
await testPlaylist('/w/p/' + playlist.shortUUID)
await testPlaylist('/videos/watch/playlist/' + playlist.id)
await testPlaylist('/videos/watch/playlist/' + playlist.uuid)
await testPlaylist('/videos/watch/playlist/' + playlist.shortUUID)
})
it('Should redirect to the origin video object', async function () {
const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + video.uuid, HttpStatusCode.FOUND_302)
expect(res.header.location).to.equal(servers[0].url + '/videos/watch/' + video.uuid)
})
it('Should return the watch action', async function () {
this.timeout(50000)
await servers[0].views.simulateViewer({ id: video.uuid, currentTimes: [ 0, 2 ] })
await processViewersStats(servers)
const res = await makeActivityPubGetRequest(servers[0].url, '/videos/local-viewer/1', HttpStatusCode.OK_200)
const object: WatchActionObject = res.body
expect(object.type).to.equal('WatchAction')
expect(object.duration).to.equal('PT2S')
expect(object.actionStatus).to.equal('CompletedActionStatus')
expect(object.watchSections).to.have.lengthOf(1)
expect(object.watchSections[0].startTimestamp).to.equal(0)
expect(object.watchSections[0].endTimestamp).to.equal(2)
})
after(async function () {
await cleanupTests(servers)
})
})
+82
ファイルの表示
@@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { SQLCommand } from '@tests/shared/sql-command.js'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test ActivityPub fetcher', function () {
let servers: PeerTubeServer[]
let sqlCommandServer1: SQLCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
servers = await createMultipleServers(3)
// Get the access tokens
await setAccessTokensToServers(servers)
const user = { username: 'user1', password: 'password' }
for (const server of servers) {
await server.users.create({ username: user.username, password: user.password })
}
const userAccessToken = await servers[0].login.getAccessToken(user)
await servers[0].videos.upload({ attributes: { name: 'video root' } })
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'bad video root' } })
await servers[0].videos.upload({ token: userAccessToken, attributes: { name: 'video user' } })
sqlCommandServer1 = new SQLCommand(servers[0])
{
const to = servers[0].url + '/accounts/user1'
const value = servers[1].url + '/accounts/user1'
await sqlCommandServer1.setActorField(to, 'url', value)
}
{
const value = servers[2].url + '/videos/watch/' + uuid
await sqlCommandServer1.setVideoField(uuid, 'url', value)
}
})
it('Should add only the video with a valid actor URL', async function () {
this.timeout(60000)
await doubleFollow(servers[0], servers[1])
await waitJobs(servers)
{
const { total, data } = await servers[0].videos.list({ sort: 'createdAt' })
expect(total).to.equal(3)
expect(data[0].name).to.equal('video root')
expect(data[1].name).to.equal('bad video root')
expect(data[2].name).to.equal('video user')
}
{
const { total, data } = await servers[1].videos.list({ sort: 'createdAt' })
expect(total).to.equal(1)
expect(data[0].name).to.equal('video root')
}
})
after(async function () {
this.timeout(20000)
await sqlCommandServer1.cleanup()
await cleanupTests(servers)
})
})
+5
ファイルの表示
@@ -0,0 +1,5 @@
import './cleaner.js'
import './client.js'
import './fetch.js'
import './refresher.js'
import './security.js'
+157
ファイルの表示
@@ -0,0 +1,157 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { SQLCommand } from '@tests/shared/sql-command.js'
import { wait } from '@peertube/peertube-core-utils'
import { HttpStatusCode, VideoPlaylistPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
killallServers,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test AP refresher', function () {
let servers: PeerTubeServer[] = []
let sqlCommandServer2: SQLCommand
let videoUUID1: string
let videoUUID2: string
let videoUUID3: string
let playlistUUID1: string
let playlistUUID2: string
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
for (const server of servers) {
await server.config.disableTranscoding()
}
{
videoUUID1 = (await servers[1].videos.quickUpload({ name: 'video1' })).uuid
videoUUID2 = (await servers[1].videos.quickUpload({ name: 'video2' })).uuid
videoUUID3 = (await servers[1].videos.quickUpload({ name: 'video3' })).uuid
}
{
const token1 = await servers[1].users.generateUserAndToken('user1')
await servers[1].videos.upload({ token: token1, attributes: { name: 'video4' } })
const token2 = await servers[1].users.generateUserAndToken('user2')
await servers[1].videos.upload({ token: token2, attributes: { name: 'video5' } })
}
{
const attributes = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].store.channel.id }
const created = await servers[1].playlists.create({ attributes })
playlistUUID1 = created.uuid
}
{
const attributes = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].store.channel.id }
const created = await servers[1].playlists.create({ attributes })
playlistUUID2 = created.uuid
}
await doubleFollow(servers[0], servers[1])
sqlCommandServer2 = new SQLCommand(servers[1])
})
describe('Videos refresher', function () {
it('Should remove a deleted remote video', async function () {
this.timeout(60000)
await wait(10000)
// Change UUID so the remote server returns a 404
await sqlCommandServer2.setVideoField(videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
await servers[0].videos.get({ id: videoUUID1 })
await servers[0].videos.get({ id: videoUUID2 })
await waitJobs(servers)
await servers[0].videos.get({ id: videoUUID1, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await servers[0].videos.get({ id: videoUUID2 })
})
it('Should not update a remote video if the remote instance is down', async function () {
this.timeout(70000)
await killallServers([ servers[1] ])
await sqlCommandServer2.setVideoField(videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
// Video will need a refresh
await wait(10000)
await servers[0].videos.get({ id: videoUUID3 })
// The refresh should fail
await waitJobs([ servers[0] ])
await servers[1].run()
await servers[0].videos.get({ id: videoUUID3 })
})
})
describe('Actors refresher', function () {
it('Should remove a deleted actor', async function () {
this.timeout(60000)
const command = servers[0].accounts
await wait(10000)
// Change actor name so the remote server returns a 404
const to = servers[1].url + '/accounts/user2'
await sqlCommandServer2.setActorField(to, 'preferredUsername', 'toto')
await command.get({ accountName: 'user1@' + servers[1].host })
await command.get({ accountName: 'user2@' + servers[1].host })
await waitJobs(servers)
await command.get({ accountName: 'user1@' + servers[1].host, expectedStatus: HttpStatusCode.OK_200 })
await command.get({ accountName: 'user2@' + servers[1].host, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('Playlist refresher', function () {
it('Should remove a deleted playlist', async function () {
this.timeout(60000)
await wait(10000)
// Change UUID so the remote server returns a 404
await sqlCommandServer2.setPlaylistField(playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
await servers[0].playlists.get({ playlistId: playlistUUID1 })
await servers[0].playlists.get({ playlistId: playlistUUID2 })
await waitJobs(servers)
await servers[0].playlists.get({ playlistId: playlistUUID1, expectedStatus: HttpStatusCode.OK_200 })
await servers[0].playlists.get({ playlistId: playlistUUID2, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
after(async function () {
await sqlCommandServer2.cleanup()
await cleanupTests(servers)
})
})
+364
ファイルの表示
@@ -0,0 +1,364 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { HttpStatusCode } from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import { PeerTubeServer, cleanupTests, createMultipleServers, killallServers } from '@peertube/peertube-server-commands'
import {
activityPubContextify,
buildGlobalHTTPHeaders,
signAndContextify
} from '@peertube/peertube-server/core/helpers/activity-pub-utils.js'
import { buildDigest } from '@peertube/peertube-server/core/helpers/peertube-crypto.js'
import { signJsonLDObject } from '@peertube/peertube-server/core/helpers/peertube-jsonld.js'
import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@peertube/peertube-server/core/initializers/constants.js'
import { makePOSTAPRequest } from '@tests/shared/requests.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
import { expect } from 'chai'
import { readJsonSync } from 'fs-extra/esm'
function signJsonLDObjectWithoutAssertion (options: Parameters<typeof signJsonLDObject>[0]) {
return signJsonLDObject({
...options,
disableWorkerThreadAssertion: true
})
}
function fakeFilter () {
return (data: any) => Promise.resolve(data)
}
function setKeysOfServer (onServer: SQLCommand, ofServerUrl: string, publicKey: string, privateKey: string) {
const url = ofServerUrl + '/accounts/peertube'
return Promise.all([
onServer.setActorField(url, 'publicKey', publicKey),
onServer.setActorField(url, 'privateKey', privateKey)
])
}
function setUpdatedAtOfServer (onServer: SQLCommand, ofServerUrl: string, updatedAt: string) {
const url = ofServerUrl + '/accounts/peertube'
return Promise.all([
onServer.setActorField(url, 'createdAt', updatedAt),
onServer.setActorField(url, 'updatedAt', updatedAt)
])
}
function getAnnounceWithoutContext (server: PeerTubeServer) {
const json = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
const result: typeof json = {}
for (const key of Object.keys(json)) {
if (Array.isArray(json[key])) {
result[key] = json[key].map(v => v.replace(':9002', `:${server.port}`))
} else {
result[key] = json[key].replace(':9002', `:${server.port}`)
}
}
return result
}
async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
const follow = {
type: 'Follow',
id: by.url + '/' + new Date().getTime(),
actor: by.url,
object: to.url
}
const body = await activityPubContextify(follow, 'Follow', fakeFilter())
const httpSignature = {
algorithm: HTTP_SIGNATURE.ALGORITHM,
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
keyId: by.url,
key: by.privateKey,
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD
}
const headers = {
'digest': buildDigest(body),
'content-type': 'application/activity+json',
'accept': ACTIVITY_PUB.ACCEPT_HEADER
}
return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
}
describe('Test ActivityPub security', function () {
let servers: PeerTubeServer[]
let sqlCommands: SQLCommand[] = []
let url: string
const keys = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/keys.json'))
const invalidKeys = readJsonSync(buildAbsoluteFixturePath('./ap-json/peertube/invalid-keys.json'))
const baseHttpSignature = () => ({
algorithm: HTTP_SIGNATURE.ALGORITHM,
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
keyId: 'acct:peertube@' + servers[1].host,
key: keys.privateKey,
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD
})
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
servers = await createMultipleServers(3)
sqlCommands = servers.map(s => new SQLCommand(s))
url = servers[0].url + '/inbox'
await setKeysOfServer(sqlCommands[0], servers[1].url, keys.publicKey, null)
await setKeysOfServer(sqlCommands[1], servers[1].url, keys.publicKey, keys.privateKey)
const to = { url: servers[0].url + '/accounts/peertube' }
const by = { url: servers[1].url + '/accounts/peertube', privateKey: keys.privateKey }
await makeFollowRequest(to, by)
})
describe('When checking HTTP signature', function () {
it('Should fail with an invalid digest', async function () {
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = {
Digest: buildDigest({ hello: 'coucou' })
}
try {
await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
it('Should fail with an invalid date', async function () {
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
try {
await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
it('Should fail with bad keys', async function () {
await setKeysOfServer(sqlCommands[0], servers[1].url, invalidKeys.publicKey, invalidKeys.privateKey)
await setKeysOfServer(sqlCommands[1], servers[1].url, invalidKeys.publicKey, invalidKeys.privateKey)
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
try {
await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
it('Should reject requests without appropriate signed headers', async function () {
await setKeysOfServer(sqlCommands[0], servers[1].url, keys.publicKey, keys.privateKey)
await setKeysOfServer(sqlCommands[1], servers[1].url, keys.publicKey, keys.privateKey)
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
const signatureOptions = baseHttpSignature()
const badHeadersMatrix = [
[ '(request-target)', 'date', 'digest' ],
[ 'host', 'date', 'digest' ],
[ '(request-target)', 'host', 'digest' ]
]
for (const badHeaders of badHeadersMatrix) {
signatureOptions.headers = badHeaders
try {
await makePOSTAPRequest(url, body, signatureOptions, headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
}
})
it('Should succeed with a valid HTTP signature draft 11 (without date but with (created))', async function () {
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
const signatureOptions = baseHttpSignature()
signatureOptions.headers = [ '(request-target)', '(created)', 'host', 'digest' ]
const { statusCode } = await makePOSTAPRequest(url, body, signatureOptions, headers)
expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
})
it('Should succeed with a valid HTTP signature', async function () {
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
})
it('Should refresh the actor keys', async function () {
this.timeout(20000)
// Update keys of server 2 to invalid keys
// Server 1 should refresh the actor and fail
await setKeysOfServer(sqlCommands[1], servers[1].url, invalidKeys.publicKey, invalidKeys.privateKey)
await setUpdatedAtOfServer(sqlCommands[0], servers[1].url, '2015-07-17 22:00:00+00')
// Invalid peertube actor cache
await killallServers([ servers[1] ])
await servers[1].run()
const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce', fakeFilter())
const headers = buildGlobalHTTPHeaders(body, buildDigest)
try {
await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
console.error(err)
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
})
describe('When checking Linked Data Signature', function () {
before(async function () {
await setKeysOfServer(sqlCommands[0], servers[1].url, keys.publicKey, keys.privateKey)
await setKeysOfServer(sqlCommands[1], servers[1].url, keys.publicKey, keys.privateKey)
await setKeysOfServer(sqlCommands[2], servers[2].url, keys.publicKey, keys.privateKey)
const to = { url: servers[0].url + '/accounts/peertube' }
const by = { url: servers[2].url + '/accounts/peertube', privateKey: keys.privateKey }
await makeFollowRequest(to, by)
})
it('Should fail with bad keys', async function () {
await setKeysOfServer(sqlCommands[0], servers[2].url, invalidKeys.publicKey, invalidKeys.privateKey)
await setKeysOfServer(sqlCommands[2], servers[2].url, invalidKeys.publicKey, invalidKeys.privateKey)
const body = getAnnounceWithoutContext(servers[1])
body.actor = servers[2].url + '/accounts/peertube'
const signer: any = { privateKey: invalidKeys.privateKey, url: servers[2].url + '/accounts/peertube' }
const signedBody = await signAndContextify({
byActor: signer,
data: body,
contextType: 'Announce',
contextFilter: fakeFilter(),
signerFunction: signJsonLDObjectWithoutAssertion
})
const headers = buildGlobalHTTPHeaders(signedBody, buildDigest)
try {
await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
it('Should fail with an altered body', async function () {
await setKeysOfServer(sqlCommands[0], servers[2].url, keys.publicKey, keys.privateKey)
await setKeysOfServer(sqlCommands[0], servers[2].url, keys.publicKey, keys.privateKey)
const body = getAnnounceWithoutContext(servers[1])
body.actor = servers[2].url + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: servers[2].url + '/accounts/peertube' }
const signedBody: any = await signAndContextify({
byActor: signer,
data: body,
contextType: 'Announce',
contextFilter: fakeFilter(),
signerFunction: signJsonLDObjectWithoutAssertion
})
signedBody.actor = servers[2].url + '/account/peertube'
const headers = buildGlobalHTTPHeaders(signedBody, buildDigest)
try {
await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
it('Should succeed with a valid signature', async function () {
const body = getAnnounceWithoutContext(servers[1])
body.actor = servers[2].url + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: servers[2].url + '/accounts/peertube' }
const signedBody = await signAndContextify({
byActor: signer,
data: body,
contextType: 'Announce',
contextFilter: fakeFilter(),
signerFunction: signJsonLDObjectWithoutAssertion
})
const headers = buildGlobalHTTPHeaders(signedBody, buildDigest)
const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
})
it('Should refresh the actor keys', async function () {
this.timeout(20000)
// Wait refresh invalidation
await wait(10000)
// Update keys of server 3 to invalid keys
// Server 1 should refresh the actor and fail
await setKeysOfServer(sqlCommands[2], servers[2].url, invalidKeys.publicKey, invalidKeys.privateKey)
const body = getAnnounceWithoutContext(servers[1])
body.actor = servers[2].url + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: servers[2].url + '/accounts/peertube' }
const signedBody = await signAndContextify({
byActor: signer,
data: body,
contextType: 'Announce',
contextFilter: fakeFilter(),
signerFunction: signJsonLDObjectWithoutAssertion
})
const headers = buildGlobalHTTPHeaders(signedBody, buildDigest)
try {
await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
expect(true, 'Did not throw').to.be.false
} catch (err) {
expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
}
})
})
after(async function () {
for (const sql of sqlCommands) {
await sql.cleanup()
}
await cleanupTests(servers)
})
})
+438
ファイルの表示
@@ -0,0 +1,438 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { AbuseCreate, AbuseState, HttpStatusCode } from '@peertube/peertube-models'
import {
AbusesCommand,
cleanupTests,
createSingleServer,
doubleFollow,
makeGetRequest,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test abuses API validators', function () {
const basePath = '/api/v1/abuses/'
let server: PeerTubeServer
let userToken = ''
let userToken2 = ''
let abuseId: number
let messageId: number
let command: AbusesCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
userToken = await server.users.generateUserAndToken('user_1')
userToken2 = await server.users.generateUserAndToken('user_2')
server.store.videoCreated = await server.videos.upload()
command = server.abuses
})
describe('When listing abuses for admins', function () {
const path = basePath
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a bad id filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 'toto' } })
})
it('Should fail with a bad filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'toto' } })
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'videos' } })
})
it('Should fail with bad predefined reason', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { predefinedReason: 'violentOrRepulsives' } })
})
it('Should fail with a bad state filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 'toto' } })
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 0 } })
})
it('Should fail with a bad videoIs filter', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { videoIs: 'toto' } })
})
it('Should succeed with the correct params', async function () {
const query = {
id: 13,
predefinedReason: 'violentOrRepulsive',
filter: 'comment',
state: 2,
videoIs: 'deleted'
}
await makeGetRequest({ url: server.url, path, token: server.accessToken, query, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When listing abuses for users', function () {
const path = '/api/v1/users/me/abuses'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, userToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, userToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, userToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad id filter', async function () {
await makeGetRequest({ url: server.url, path, token: userToken, query: { id: 'toto' } })
})
it('Should fail with a bad state filter', async function () {
await makeGetRequest({ url: server.url, path, token: userToken, query: { state: 'toto' } })
await makeGetRequest({ url: server.url, path, token: userToken, query: { state: 0 } })
})
it('Should succeed with the correct params', async function () {
const query = {
id: 13,
state: 2
}
await makeGetRequest({ url: server.url, path, token: userToken, query, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When reporting an abuse', function () {
const path = basePath
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with a wrong video', async function () {
const fields = { video: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with an unknown video', async function () {
const fields = { video: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({
url: server.url,
path,
token: userToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a wrong comment', async function () {
const fields = { comment: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with an unknown comment', async function () {
const fields = { comment: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({
url: server.url,
path,
token: userToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a wrong account', async function () {
const fields = { account: { id: 'blabla' }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with an unknown account', async function () {
const fields = { account: { id: 42 }, reason: 'my super reason' }
await makePostBodyRequest({
url: server.url,
path,
token: userToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with not account, comment or video', async function () {
const fields = { reason: 'my super reason' }
await makePostBodyRequest({
url: server.url,
path,
token: userToken,
fields,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a non authenticated user', async function () {
const fields = { video: { id: server.store.videoCreated.id }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a reason too short', async function () {
const fields = { video: { id: server.store.videoCreated.id }, reason: 'h' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with a too big reason', async function () {
const fields = { video: { id: server.store.videoCreated.id }, reason: 'super'.repeat(605) }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should succeed with the correct parameters (basic)', async function () {
const fields: AbuseCreate = { video: { id: server.store.videoCreated.shortUUID }, reason: 'my super reason' }
const res = await makePostBodyRequest({
url: server.url,
path,
token: userToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
abuseId = res.body.abuse.id
})
it('Should fail with a wrong predefined reason', async function () {
const fields = { video: server.store.videoCreated, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail with negative timestamps', async function () {
const fields = { video: { id: server.store.videoCreated.id, startAt: -1 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should fail mith misordered startAt/endAt', async function () {
const fields = { video: { id: server.store.videoCreated.id, startAt: 5, endAt: 1 }, reason: 'my super reason' }
await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
})
it('Should succeed with the correct parameters (advanced)', async function () {
const fields: AbuseCreate = {
video: {
id: server.store.videoCreated.id,
startAt: 1,
endAt: 5
},
reason: 'my super reason',
predefinedReasons: [ 'serverRules' ]
}
await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When updating an abuse', function () {
it('Should fail with a non authenticated user', async function () {
await command.update({ token: 'blabla', abuseId, body: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a non admin user', async function () {
await command.update({ token: userToken, abuseId, body: {}, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad abuse id', async function () {
await command.update({ abuseId: 45, body: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a bad state', async function () {
const body = { state: 5 as any }
await command.update({ abuseId, body, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad moderation comment', async function () {
const body = { moderationComment: 'b'.repeat(3001) }
await command.update({ abuseId, body, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct params', async function () {
const body = { state: AbuseState.ACCEPTED }
await command.update({ abuseId, body })
})
})
describe('When creating an abuse message', function () {
const message = 'my super message'
it('Should fail with an invalid abuse id', async function () {
await command.addMessage({ token: userToken2, abuseId: 888, message, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non authenticated user', async function () {
await command.addMessage({ token: 'fake_token', abuseId, message, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an invalid logged in user', async function () {
await command.addMessage({ token: userToken2, abuseId, message, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an invalid message', async function () {
await command.addMessage({ token: userToken, abuseId, message: 'a'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct params', async function () {
const res = await command.addMessage({ token: userToken, abuseId, message })
messageId = res.body.abuseMessage.id
})
})
describe('When listing abuse messages', function () {
it('Should fail with an invalid abuse id', async function () {
await command.listMessages({ token: userToken, abuseId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non authenticated user', async function () {
await command.listMessages({ token: 'fake_token', abuseId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an invalid logged in user', async function () {
await command.listMessages({ token: userToken2, abuseId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await command.listMessages({ token: userToken, abuseId })
})
})
describe('When deleting an abuse message', function () {
it('Should fail with an invalid abuse id', async function () {
await command.deleteMessage({ token: userToken, abuseId: 888, messageId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an invalid message id', async function () {
await command.deleteMessage({ token: userToken, abuseId, messageId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non authenticated user', async function () {
await command.deleteMessage({ token: 'fake_token', abuseId, messageId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an invalid logged in user', async function () {
await command.deleteMessage({ token: userToken2, abuseId, messageId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await command.deleteMessage({ token: userToken, abuseId, messageId })
})
})
describe('When deleting a video abuse', function () {
it('Should fail with a non authenticated user', async function () {
await command.delete({ token: 'blabla', abuseId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a non admin user', async function () {
await command.delete({ token: userToken, abuseId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad abuse id', async function () {
await command.delete({ abuseId: 45, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await command.delete({ abuseId })
})
})
describe('When trying to manage messages of a remote abuse', function () {
let remoteAbuseId: number
let anotherServer: PeerTubeServer
before(async function () {
this.timeout(50000)
anotherServer = await createSingleServer(2)
await setAccessTokensToServers([ anotherServer ])
await doubleFollow(anotherServer, server)
const server2VideoId = await anotherServer.videos.getId({ uuid: server.store.videoCreated.uuid })
await anotherServer.abuses.report({ reason: 'remote server', videoId: server2VideoId })
await waitJobs([ server, anotherServer ])
const body = await command.getAdminList({ sort: '-createdAt' })
remoteAbuseId = body.data[0].id
})
it('Should fail when listing abuse messages of a remote abuse', async function () {
await command.listMessages({ abuseId: remoteAbuseId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail when creating abuse message of a remote abuse', async function () {
await command.addMessage({ abuseId: remoteAbuseId, message: 'message', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
after(async function () {
await cleanupTests([ anotherServer ])
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+43
ファイルの表示
@@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands'
describe('Test accounts API validators', function () {
const path = '/api/v1/accounts/'
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
})
describe('When listing accounts', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
})
describe('When getting an account', function () {
it('Should return 404 with a non existing name', async function () {
await server.accounts.get({ accountName: 'arfaze', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+137
ファイルの表示
@@ -0,0 +1,137 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createSingleServer, setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test auto tag policies API validator', function () {
let server: PeerTubeServer
let userToken: string
let userToken2: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
userToken = await server.users.generateUserAndToken('user1')
userToken2 = await server.users.generateUserAndToken('user2')
})
describe('When getting available account auto tags', function () {
const baseParams = () => ({ accountName: 'user1', token: userToken })
it('Should fail without token', async function () {
await server.autoTags.getAccountAvailable({ ...baseParams(), token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a user that cannot manage account', async function () {
await server.autoTags.getAccountAvailable({ ...baseParams(), token: userToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an unknown account', async function () {
await server.autoTags.getAccountAvailable({ ...baseParams(), accountName: 'user42', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.autoTags.getAccountAvailable(baseParams())
})
})
describe('When getting available server auto tags', function () {
it('Should fail without token', async function () {
await server.autoTags.getServerAvailable({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a user that that does not have enought rights', async function () {
await server.autoTags.getServerAvailable({ token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await server.autoTags.getServerAvailable()
})
})
describe('When getting auto tag policies', function () {
const baseParams = () => ({ accountName: 'user1', token: userToken })
it('Should fail without token', async function () {
await server.autoTags.getCommentPolicies({ ...baseParams(), token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a user that cannot manage account', async function () {
await server.autoTags.getCommentPolicies({ ...baseParams(), token: userToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an unknown account', async function () {
await server.autoTags.getCommentPolicies({ ...baseParams(), accountName: 'user42', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.autoTags.getCommentPolicies(baseParams())
})
})
describe('When updating auto tag policies', function () {
const baseParams = () => ({ accountName: 'user1', review: [ 'external-link' ], token: userToken })
it('Should fail without token', async function () {
await server.autoTags.updateCommentPolicies({
...baseParams(),
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user that cannot manage account', async function () {
await server.autoTags.updateCommentPolicies({
...baseParams(),
token: userToken2,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown account', async function () {
await server.autoTags.updateCommentPolicies({
...baseParams(),
accountName: 'user42',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with invalid review array', async function () {
await server.autoTags.updateCommentPolicies({
...baseParams(),
review: 'toto' as any,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with review array that does not contain available tags', async function () {
await server.autoTags.updateCommentPolicies({
...baseParams(),
review: [ 'toto' ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct params', async function () {
await server.autoTags.updateCommentPolicies(baseParams())
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+556
ファイルの表示
@@ -0,0 +1,556 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test blocklist API validators', function () {
let servers: PeerTubeServer[]
let server: PeerTubeServer
let userAccessToken: string
before(async function () {
this.timeout(60000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
server = servers[0]
const user = { username: 'user1', password: 'password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
await doubleFollow(servers[0], servers[1])
})
// ---------------------------------------------------------------
describe('When managing user blocklist', function () {
describe('When managing user accounts blocklist', function () {
const path = '/api/v1/users/me/blocklist/accounts'
describe('When listing blocked accounts', function () {
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
})
describe('When blocking an account', function () {
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { accountName: 'user1' },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with an unknown account', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user2' },
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail to block ourselves', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'root' },
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user1' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When unblocking an account', function () {
it('Should fail with an unauthenticated user', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with an unknown account block', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user2',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct params', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1',
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
})
describe('When managing user servers blocklist', function () {
const path = '/api/v1/users/me/blocklist/servers'
describe('When listing blocked servers', function () {
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
})
describe('When blocking a server', function () {
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { host: '127.0.0.1:9002' },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with an unknown server', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: '127.0.0.1:9003' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should fail with our own server', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: server.host },
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: servers[1].host },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When unblocking a server', function () {
it('Should fail with an unauthenticated user', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/' + servers[1].host,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with an unknown server block', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/127.0.0.1:9004',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct params', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/' + servers[1].host,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
})
})
describe('When managing server blocklist', function () {
describe('When managing server accounts blocklist', function () {
const path = '/api/v1/server/blocklist/accounts'
describe('When listing blocked accounts', function () {
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
})
describe('When blocking an account', function () {
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { accountName: 'user1' },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makePostBodyRequest({
url: server.url,
token: userAccessToken,
path,
fields: { accountName: 'user1' },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown account', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user2' },
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail to block ourselves', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'root' },
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user1' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When unblocking an account', function () {
it('Should fail with an unauthenticated user', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1',
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown account block', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user2',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct params', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1',
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
})
describe('When managing server servers blocklist', function () {
const path = '/api/v1/server/blocklist/servers'
describe('When listing blocked servers', function () {
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
})
describe('When blocking a server', function () {
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { host: servers[1].host },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makePostBodyRequest({
url: server.url,
token: userAccessToken,
path,
fields: { host: servers[1].host },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with an unknown server', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: '127.0.0.1:9003' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should fail with our own server', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: server.host },
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { host: servers[1].host },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When unblocking a server', function () {
it('Should fail with an unauthenticated user', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/' + servers[1].host,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a user without the appropriate rights', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/' + servers[1].host,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown server block', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/127.0.0.1:9004',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct params', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/' + servers[1].host,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
})
})
describe('When getting blocklist status', function () {
const path = '/api/v1/blocklist/status'
it('Should fail with a bad token', async function () {
await makeGetRequest({
url: server.url,
path,
token: 'false',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad accounts field', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
accounts: 1
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeGetRequest({
url: server.url,
path,
query: {
accounts: [ 1 ]
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad hosts field', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
hosts: 1
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeGetRequest({
url: server.url,
path,
query: {
hosts: [ 1 ]
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
query: {},
expectedStatus: HttpStatusCode.OK_200
})
await makeGetRequest({
url: server.url,
path,
query: {
hosts: [ 'example.com' ],
accounts: [ 'john@example.com' ]
},
expectedStatus: HttpStatusCode.OK_200
})
})
})
after(async function () {
await cleanupTests(servers)
})
})
+86
ファイルの表示
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test bulk API validators', function () {
let server: PeerTubeServer
let userAccessToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = { username: 'user1', password: 'password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When removing comments of', function () {
const path = '/api/v1/bulk/remove-comments-of'
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { accountName: 'user1', scope: 'my-videos' },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with an unknown account', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user2', scope: 'my-videos' },
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an invalid scope', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user1', scope: 'my-videoss' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to delete comments of the instance without the appropriate rights', async function () {
await makePostBodyRequest({
url: server.url,
token: userAccessToken,
path,
fields: { accountName: 'user1', scope: 'instance' },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path,
fields: { accountName: 'user1', scope: 'instance' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+209
ファイルの表示
@@ -0,0 +1,209 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { areHttpImportTestsDisabled } from '@peertube/peertube-node-utils'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
ChannelsCommand,
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test videos import in a channel API validator', function () {
let server: PeerTubeServer
const userInfo = {
accessToken: '',
channelName: 'fake_channel',
channelId: -1,
id: -1,
videoQuota: -1,
videoQuotaDaily: -1,
channelSyncId: -1
}
let command: ChannelsCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableVideoImports()
await server.config.enableChannelSync()
const userCreds = {
username: 'fake',
password: 'fake_password'
}
{
const user = await server.users.create({ username: userCreds.username, password: userCreds.password })
userInfo.id = user.id
userInfo.accessToken = await server.login.getAccessToken(userCreds)
const info = await server.users.getMyInfo({ token: userInfo.accessToken })
userInfo.channelId = info.videoChannels[0].id
}
{
const { videoChannelSync } = await server.channelSyncs.create({
token: userInfo.accessToken,
attributes: {
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelId: userInfo.channelId
}
})
userInfo.channelSyncId = videoChannelSync.id
}
command = server.channels
})
it('Should fail when HTTP upload is disabled', async function () {
await server.config.disableChannelSync()
await server.config.disableVideoImports()
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: server.accessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
await server.config.enableVideoImports()
})
it('Should fail when externalChannelUrl is not provided', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: null,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail when externalChannelUrl is malformed', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: 'not-a-url',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad sync id', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelSyncId: 'toto' as any,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a unknown sync id', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelSyncId: 42,
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a sync id of another channel', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelSyncId: userInfo.channelSyncId,
token: server.accessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with no authentication', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail when sync is not owned by the user', async function () {
await command.importVideos({
channelName: server.store.channel.name,
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: userInfo.accessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail when the user has no quota', async function () {
await server.users.update({
userId: userInfo.id,
videoQuota: 0
})
await command.importVideos({
channelName: 'fake_channel',
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: userInfo.accessToken,
expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
})
await server.users.update({
userId: userInfo.id,
videoQuota: userInfo.videoQuota
})
})
it('Should fail when the user has no daily quota', async function () {
await server.users.update({
userId: userInfo.id,
videoQuotaDaily: 0
})
await command.importVideos({
channelName: 'fake_channel',
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: userInfo.accessToken,
expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
})
await server.users.update({
userId: userInfo.id,
videoQuotaDaily: userInfo.videoQuotaDaily
})
})
it('Should succeed when sync is run by its owner', async function () {
if (!areHttpImportTestsDisabled()) return
await command.importVideos({
channelName: 'fake_channel',
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
token: userInfo.accessToken
})
})
it('Should succeed when sync is run with root and for another user\'s channel', async function () {
if (!areHttpImportTestsDisabled()) return
await command.importVideos({
channelName: 'fake_channel',
externalChannelUrl: FIXTURE_URLS.youtubeChannel
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+305
ファイルの表示
@@ -0,0 +1,305 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import merge from 'lodash-es/merge.js'
import { omit } from '@peertube/peertube-core-utils'
import { ActorImageType, CustomConfig, HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePutBodyRequest,
makeUploadRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
describe('Test config API validators', function () {
const path = '/api/v1/config/custom'
let server: PeerTubeServer
let userAccessToken: string
let updateParams: CustomConfig
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
updateParams = await server.config.getCustomConfig()
const user = {
username: 'user1',
password: 'password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When getting the configuration', function () {
it('Should fail without token', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('When updating the configuration', function () {
it('Should fail without token', async function () {
await makePutBodyRequest({
url: server.url,
path,
fields: updateParams,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePutBodyRequest({
url: server.url,
path,
fields: updateParams,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail if it misses a key', async function () {
const newUpdateParams = { ...updateParams, admin: omit(updateParams.admin, [ 'email' ]) }
await makePutBodyRequest({
url: server.url,
path,
fields: newUpdateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad default NSFW policy', async function () {
const newUpdateParams = {
...updateParams,
instance: {
defaultNSFWPolicy: 'hello'
}
}
await makePutBodyRequest({
url: server.url,
path,
fields: newUpdateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if email disabled and signup requires email verification', async function () {
// opposite scenario - success when enable enabled - covered via tests/api/users/user-verification.ts
const newUpdateParams = {
...updateParams,
signup: {
enabled: true,
limit: 5,
requiresApproval: true,
requiresEmailVerification: true
}
}
await makePutBodyRequest({
url: server.url,
path,
fields: newUpdateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a disabled web videos & hls transcoding', async function () {
const newUpdateParams = {
...updateParams,
transcoding: {
hls: {
enabled: false
},
web_videos: {
enabled: false
}
}
}
await makePutBodyRequest({
url: server.url,
path,
fields: newUpdateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a disabled http upload & enabled sync', async function () {
const newUpdateParams: CustomConfig = merge({}, updateParams, {
import: {
videos: {
http: { enabled: false }
},
videoChannelSynchronization: { enabled: true }
}
})
await makePutBodyRequest({
url: server.url,
path,
fields: newUpdateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: server.url,
path,
fields: updateParams,
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When deleting the configuration', function () {
it('Should fail without token', async function () {
await makeDeleteRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeDeleteRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('Updating instance image', function () {
const toTest = [
{ path: '/api/v1/config/instance-banner/pick', attachName: 'bannerfile' },
{ path: '/api/v1/config/instance-avatar/pick', attachName: 'avatarfile' }
]
it('Should fail with an incorrect input file', async function () {
for (const { attachName, path } of toTest) {
const attaches = { [attachName]: buildAbsoluteFixturePath('video_short.mp4') }
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields: {}, attaches })
}
})
it('Should fail with a big file', async function () {
for (const { attachName, path } of toTest) {
const attaches = { [attachName]: buildAbsoluteFixturePath('avatar-big.png') }
await makeUploadRequest({
url: server.url,
path,
token: server.accessToken,
fields: {},
attaches,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
})
it('Should fail without token', async function () {
for (const { attachName, path } of toTest) {
const attaches = { [attachName]: buildAbsoluteFixturePath('avatar.png') }
await makeUploadRequest({
url: server.url,
path,
fields: {},
attaches,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should fail without the appropriate rights', async function () {
for (const { attachName, path } of toTest) {
const attaches = { [attachName]: buildAbsoluteFixturePath('avatar.png') }
await makeUploadRequest({
url: server.url,
path,
token: userAccessToken,
fields: {},
attaches,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should succeed with the correct params', async function () {
for (const { attachName, path } of toTest) {
const attaches = { [attachName]: buildAbsoluteFixturePath('avatar.png') }
await makeUploadRequest({
url: server.url,
path,
token: server.accessToken,
fields: {},
attaches,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
}
})
})
describe('Deleting instance image', function () {
const types = [ ActorImageType.BANNER, ActorImageType.AVATAR ]
it('Should fail without token', async function () {
for (const type of types) {
await server.config.deleteInstanceImage({ type, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}
})
it('Should fail without the appropriate rights', async function () {
for (const type of types) {
await server.config.deleteInstanceImage({ type, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should succeed with the correct params', async function () {
for (const type of types) {
await server.config.deleteInstanceImage({ type })
}
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+86
ファイルの表示
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
ConfigCommand,
ContactFormCommand,
createSingleServer,
killallServers,
PeerTubeServer
} from '@peertube/peertube-server-commands'
describe('Test contact form API validators', function () {
let server: PeerTubeServer
const emails: object[] = []
const defaultBody = {
fromName: 'super name',
fromEmail: 'toto@example.com',
subject: 'my subject',
body: 'Hello, how are you?'
}
let emailPort: number
let command: ContactFormCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
emailPort = await MockSmtpServer.Instance.collectEmails(emails)
// Email is disabled
server = await createSingleServer(1)
command = server.contactForm
})
it('Should not accept a contact form if emails are disabled', async function () {
await command.send({ ...defaultBody, expectedStatus: HttpStatusCode.CONFLICT_409 })
})
it('Should not accept a contact form if it is disabled in the configuration', async function () {
this.timeout(25000)
await killallServers([ server ])
// Contact form is disabled
await server.run({ ...ConfigCommand.getEmailOverrideConfig(emailPort), contact_form: { enabled: false } })
await command.send({ ...defaultBody, expectedStatus: HttpStatusCode.CONFLICT_409 })
})
it('Should not accept a contact form if from email is invalid', async function () {
this.timeout(25000)
await killallServers([ server ])
// Email & contact form enabled
await server.run(ConfigCommand.getEmailOverrideConfig(emailPort))
await command.send({ ...defaultBody, fromEmail: 'badEmail', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, fromEmail: 'badEmail@', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, fromEmail: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should not accept a contact form if from name is invalid', async function () {
await command.send({ ...defaultBody, fromName: 'name'.repeat(100), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, fromName: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, fromName: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should not accept a contact form if body is invalid', async function () {
await command.send({ ...defaultBody, body: 'body'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, body: 'a', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.send({ ...defaultBody, body: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should accept a contact form with the correct parameters', async function () {
await command.send(defaultBody)
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests([ server ])
})
})
+79
ファイルの表示
@@ -0,0 +1,79 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test custom pages validators', function () {
const path = '/api/v1/custom-pages/homepage/instance'
let server: PeerTubeServer
let userAccessToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = { username: 'user1', password: 'password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When updating instance homepage', function () {
it('Should fail with an unauthenticated user', async function () {
await makePutBodyRequest({
url: server.url,
path,
fields: { content: 'super content' },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: userAccessToken,
fields: { content: 'super content' },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { content: 'super content' },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When getting instance homapage', function () {
it('Should succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.OK_200
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+67
ファイルの表示
@@ -0,0 +1,67 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test debug API validators', function () {
const path = '/api/v1/server/debug'
let server: PeerTubeServer
let userAccessToken = ''
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = {
username: 'user1',
password: 'my super password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When getting debug endpoint', function () {
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: { startDate: new Date().toISOString() },
expectedStatus: HttpStatusCode.OK_200
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+369
ファイルの表示
@@ -0,0 +1,369 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test server follows API validators', function () {
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
})
describe('When managing following', function () {
let userAccessToken = null
before(async function () {
userAccessToken = await server.users.generateUserAndToken('user1')
})
describe('When adding follows', function () {
const path = '/api/v1/server/following'
it('Should fail with nothing', async function () {
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if hosts is not composed by hosts', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { hosts: [ '127.0.0.1:9002', '127.0.0.1:coucou' ] },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if hosts is composed with http schemes', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { hosts: [ '127.0.0.1:9002', 'http://127.0.0.1:9003' ] },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if hosts are not unique', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { urls: [ '127.0.0.1:9002', '127.0.0.1:9002' ] },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if handles is not composed by handles', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { handles: [ 'hello@example.com', '127.0.0.1:9001' ] },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if handles are not unique', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { urls: [ 'hello@example.com', 'hello@example.com' ] },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an invalid token', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { hosts: [ '127.0.0.1:9002' ] },
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { hosts: [ '127.0.0.1:9002' ] },
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('When listing followings', function () {
const path = '/api/v1/server/following'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path)
})
it('Should fail with an incorrect state', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
state: 'blabla'
}
})
})
it('Should fail with an incorrect actor type', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
actorType: 'blabla'
}
})
})
it('Should fail succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.OK_200,
query: {
state: 'accepted',
actorType: 'Application'
}
})
})
})
describe('When listing followers', function () {
const path = '/api/v1/server/followers'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path)
})
it('Should fail with an incorrect actor type', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
actorType: 'blabla'
}
})
})
it('Should fail with an incorrect state', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
state: 'blabla',
actorType: 'Application'
}
})
})
it('Should fail succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.OK_200,
query: {
state: 'accepted'
}
})
})
})
describe('When removing a follower', function () {
const path = '/api/v1/server/followers'
it('Should fail with an invalid token', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002',
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002',
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid follower', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/toto',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown follower', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9003',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
})
describe('When accepting a follower', function () {
const path = '/api/v1/server/followers'
it('Should fail with an invalid token', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002/accept',
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002/accept',
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid follower', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto/accept',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown follower', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9003/accept',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
})
describe('When rejecting a follower', function () {
const path = '/api/v1/server/followers'
it('Should fail with an invalid token', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002/reject',
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9002/reject',
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid follower', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto/reject',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown follower', async function () {
await makePostBodyRequest({
url: server.url,
path: path + '/toto@127.0.0.1:9003/reject',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
})
describe('When removing following', function () {
const path = '/api/v1/server/following'
it('Should fail with an invalid token', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/127.0.0.1:9002',
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/127.0.0.1:9002',
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail if we do not follow this server', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/example.com',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+51
ファイルの表示
@@ -0,0 +1,51 @@
import './abuses.js'
import './accounts.js'
import './auto-tags.js'
import './blocklist.js'
import './bulk.js'
import './channel-import-videos.js'
import './config.js'
import './contact-form.js'
import './custom-pages.js'
import './debug.js'
import './follows.js'
import './jobs.js'
import './live.js'
import './logs.js'
import './metrics.js'
import './my-user.js'
import './plugins.js'
import './redundancy.js'
import './registrations.js'
import './runners.js'
import './search.js'
import './services.js'
import './transcoding.js'
import './two-factor.js'
import './upload-quota.js'
import './user-export.js'
import './user-import.js'
import './user-notifications.js'
import './user-subscriptions.js'
import './users-admin.js'
import './users-emails.js'
import './video-blacklist.js'
import './video-captions.js'
import './video-channel-syncs.js'
import './video-channels.js'
import './video-chapters.js'
import './video-comments.js'
import './video-files.js'
import './video-imports.js'
import './video-playlists.js'
import './video-source.js'
import './video-storyboards.js'
import './video-studio.js'
import './video-token.js'
import './video-transcription.js'
import './videos-common-filters.js'
import './videos-history.js'
import './videos-overviews.js'
import './videos.js'
import './views.js'
import './watched-words.js'
+125
ファイルの表示
@@ -0,0 +1,125 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test jobs API validators', function () {
const path = '/api/v1/jobs/failed'
let server: PeerTubeServer
let userAccessToken = ''
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = {
username: 'user1',
password: 'my super password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When listing jobs', function () {
it('Should fail with a bad state', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path: path + 'ade'
})
})
it('Should fail with an incorrect job type', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: {
jobType: 'toto'
}
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('When pausing/resuming the job queue', async function () {
const commands = [ 'pause', 'resume' ]
it('Should fail with a non authenticated user', async function () {
for (const command of commands) {
await makePostBodyRequest({
url: server.url,
path: '/api/v1/jobs/' + command,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should fail with a non admin user', async function () {
for (const command of commands) {
await makePostBodyRequest({
url: server.url,
path: '/api/v1/jobs/' + command,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should succeed with the correct params', async function () {
for (const command of commands) {
await makePostBodyRequest({
url: server.url,
path: '/api/v1/jobs/' + command,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
}
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+576
ファイルの表示
@@ -0,0 +1,576 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { omit } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
LiveVideoCreate,
LiveVideoLatencyMode,
VideoCommentPolicy,
VideoCreateResult,
VideoPrivacy
} from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
LiveCommand,
PeerTubeServer,
cleanupTests,
createSingleServer,
makePostBodyRequest,
makeUploadRequest,
sendRTMPStream,
setAccessTokensToServers,
stopFfmpeg
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test video lives API validator', function () {
const path = '/api/v1/videos/live'
let server: PeerTubeServer
let userAccessToken = ''
let channelId: number
let video: VideoCreateResult
let videoIdNotLive: number
let command: LiveCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await server.config.enableMinimumTranscoding()
await server.config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
latencySetting: {
enabled: false
},
maxInstanceLives: 20,
maxUserLives: 20,
allowReplay: true
}
}
})
const username = 'user1'
const password = 'my super password'
await server.users.create({ username, password })
userAccessToken = await server.login.getAccessToken({ username, password })
{
const { videoChannels } = await server.users.getMyInfo()
channelId = videoChannels[0].id
}
{
videoIdNotLive = (await server.videos.quickUpload({ name: 'not live' })).id
}
command = server.live
})
describe('When creating a live', function () {
let baseCorrectParams: LiveVideoCreate
before(function () {
baseCorrectParams = {
name: 'my super name',
category: 5,
licence: 1,
language: 'pt',
nsfw: false,
commentsPolicy: VideoCommentPolicy.ENABLED,
downloadEnabled: true,
waitTranscoding: true,
description: 'my super description',
support: 'my super support text',
tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC,
channelId,
saveReplay: false,
replaySettings: undefined,
permanentLive: true,
latencyMode: LiveVideoLatencyMode.DEFAULT
}
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad category', async function () {
const fields = { ...baseCorrectParams, category: 125 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad licence', async function () {
const fields = { ...baseCorrectParams, licence: 125 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad language', async function () {
const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with bad comments policy', async function () {
const fields = { ...baseCorrectParams, commentsPolicy: 42 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail without a channel', async function () {
const fields = omit(baseCorrectParams, [ 'channelId' ])
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad channel', async function () {
const fields = { ...baseCorrectParams, channelId: 545454 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad privacy for replay settings', async function () {
const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: 999 } }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with another user channel', async function () {
const user = {
username: 'fake',
password: 'fake_password'
}
await server.users.create({ username: user.username, password: user.password })
const accessTokenUser = await server.login.getAccessToken(user)
const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
const customChannelId = videoChannels[0].id
const fields = { ...baseCorrectParams, channelId: customChannelId }
await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
})
it('Should fail with too many tags', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a tag length too low', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a tag length too big', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an incorrect thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with a big thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with an incorrect preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with a big preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with bad latency setting', async function () {
const fields = { ...baseCorrectParams, latencyMode: 42 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail to set latency if the server does not allow it', async function () {
const fields = { ...baseCorrectParams, latencyMode: LiveVideoLatencyMode.HIGH_LATENCY }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct parameters', async function () {
this.timeout(30000)
const res = await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.OK_200
})
video = res.body.video
})
it('Should forbid if live is disabled', async function () {
await server.config.updateExistingConfig({
newConfig: {
live: {
enabled: false
}
}
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should forbid to save replay if not enabled by the admin', async function () {
const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } }
await server.config.enableLive({ allowReplay: false, transcoding: false })
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should allow to save replay if enabled by the admin', async function () {
const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } }
await server.config.enableLive({ allowReplay: true, transcoding: false })
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should not allow live if max instance lives is reached', async function () {
await server.config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
maxInstanceLives: 1
}
}
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should not allow live if max user lives is reached', async function () {
await server.config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
maxInstanceLives: 20,
maxUserLives: 1
}
}
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('When getting live information', function () {
it('Should fail with a bad access token', async function () {
await command.get({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should not display private information without access token', async function () {
const live = await command.get({ token: '', videoId: video.id })
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
expect(live.latencyMode).to.exist
})
it('Should not display private information with token of another user', async function () {
const live = await command.get({ token: userAccessToken, videoId: video.id })
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
expect(live.latencyMode).to.exist
})
it('Should display private information with appropriate token', async function () {
const live = await command.get({ videoId: video.id })
expect(live.rtmpUrl).to.exist
expect(live.streamKey).to.exist
expect(live.latencyMode).to.exist
})
it('Should fail with a bad video id', async function () {
await command.get({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown video id', async function () {
await command.get({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non live video', async function () {
await command.get({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await command.get({ videoId: video.id })
await command.get({ videoId: video.uuid })
await command.get({ videoId: video.shortUUID })
})
})
describe('When getting live sessions', function () {
it('Should fail with a bad access token', async function () {
await command.listSessions({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without token', async function () {
await command.listSessions({ token: null, videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with the token of another user', async function () {
await command.listSessions({ token: userAccessToken, videoId: video.id, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad video id', async function () {
await command.listSessions({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown video id', async function () {
await command.listSessions({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non live video', async function () {
await command.listSessions({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await command.listSessions({ videoId: video.id })
})
})
describe('When getting live session of a replay', function () {
it('Should fail with a bad video id', async function () {
await command.getReplaySession({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown video id', async function () {
await command.getReplaySession({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non replay video', async function () {
await command.getReplaySession({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('When updating live information', async function () {
it('Should fail without access token', async function () {
await command.update({ token: '', videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a bad access token', async function () {
await command.update({ token: 'toto', videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with access token of another user', async function () {
await command.update({ token: userAccessToken, videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad video id', async function () {
await command.update({ videoId: 'toto', fields: {}, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown video id', async function () {
await command.update({ videoId: 454555, fields: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a non live video', async function () {
await command.update({ videoId: videoIdNotLive, fields: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with bad latency setting', async function () {
const fields = { latencyMode: 42 as any }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad privacy for replay settings', async function () {
const fields = { saveReplay: true, replaySettings: { privacy: 999 as any } }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with save replay enabled but without replay settings', async function () {
await server.config.enableLive({ allowReplay: true, transcoding: false })
const fields = { saveReplay: true }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with save replay disabled and replay settings', async function () {
const fields = { saveReplay: false, replaySettings: { privacy: VideoPrivacy.INTERNAL } }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with only replay settings when save replay is disabled', async function () {
const fields = { replaySettings: { privacy: VideoPrivacy.INTERNAL } }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail to set latency if the server does not allow it', async function () {
const fields = { latencyMode: LiveVideoLatencyMode.HIGH_LATENCY }
await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await command.update({ videoId: video.id, fields: { saveReplay: false } })
await command.update({ videoId: video.uuid, fields: { saveReplay: false } })
await command.update({ videoId: video.shortUUID, fields: { saveReplay: false } })
await command.update({ videoId: video.id, fields: { saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } } })
})
it('Should fail to update replay status if replay is not allowed on the instance', async function () {
await server.config.enableLive({ allowReplay: false, transcoding: false })
await command.update({ videoId: video.id, fields: { saveReplay: true }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed to live attributes if it has already started', async function () {
this.timeout(40000)
const live = await command.get({ videoId: video.id })
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await command.waitUntilPublished({ videoId: video.id })
await command.update({ videoId: video.id, fields: { permanentLive: false }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await stopFfmpeg(ffmpegCommand)
})
it('Should fail to change live privacy if it has already started', async function () {
this.timeout(40000)
const live = await command.get({ videoId: video.id })
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await command.waitUntilPublished({ videoId: video.id })
await server.videos.update({
id: video.id,
attributes: { privacy: VideoPrivacy.PUBLIC } // Same privacy, it's fine
})
await server.videos.update({
id: video.id,
attributes: { privacy: VideoPrivacy.UNLISTED },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await stopFfmpeg(ffmpegCommand)
})
it('Should fail to stream twice in the save live', async function () {
this.timeout(40000)
const live = await command.get({ videoId: video.id })
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await command.waitUntilPublished({ videoId: video.id })
await command.runAndTestStreamError({ videoId: video.id, shouldHaveError: true })
await stopFfmpeg(ffmpegCommand)
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+163
ファイルの表示
@@ -0,0 +1,163 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test logs API validators', function () {
const path = '/api/v1/server/logs'
let server: PeerTubeServer
let userAccessToken = ''
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = {
username: 'user1',
password: 'my super password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When getting logs', function () {
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a missing startDate query', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad startDate query', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: { startDate: 'toto' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad endDate query', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: { startDate: new Date().toISOString(), endDate: 'toto' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad level parameter', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: { startDate: new Date().toISOString(), level: 'toto' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: { startDate: new Date().toISOString() },
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When creating client logs', function () {
const base = {
level: 'warn' as 'warn',
message: 'my super message',
url: 'https://example.com/toto'
}
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
it('Should fail with an invalid level', async function () {
await server.logs.createLogClient({ payload: { ...base, level: '' as any }, expectedStatus })
await server.logs.createLogClient({ payload: { ...base, level: undefined }, expectedStatus })
await server.logs.createLogClient({ payload: { ...base, level: 'toto' as any }, expectedStatus })
})
it('Should fail with an invalid message', async function () {
await server.logs.createLogClient({ payload: { ...base, message: undefined }, expectedStatus })
await server.logs.createLogClient({ payload: { ...base, message: '' }, expectedStatus })
await server.logs.createLogClient({ payload: { ...base, message: 'm'.repeat(2500) }, expectedStatus })
})
it('Should fail with an invalid url', async function () {
await server.logs.createLogClient({ payload: { ...base, url: undefined }, expectedStatus })
await server.logs.createLogClient({ payload: { ...base, url: 'toto' }, expectedStatus })
})
it('Should fail with an invalid stackTrace', async function () {
await server.logs.createLogClient({ payload: { ...base, stackTrace: 's'.repeat(20000) }, expectedStatus })
})
it('Should fail with an invalid userAgent', async function () {
await server.logs.createLogClient({ payload: { ...base, userAgent: 's'.repeat(500) }, expectedStatus })
})
it('Should fail with an invalid meta', async function () {
await server.logs.createLogClient({ payload: { ...base, meta: 's'.repeat(20000) }, expectedStatus })
})
it('Should succeed with the correct params', async function () {
await server.logs.createLogClient({ payload: { ...base, stackTrace: 'stackTrace', meta: '{toto}', userAgent: 'userAgent' } })
})
it('Should rate limit log creation', async function () {
let fail = false
for (let i = 0; i < 100; i++) {
try {
await server.logs.createLogClient({ token: null, payload: base })
} catch {
fail = true
}
}
expect(fail).to.be.true
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+214
ファイルの表示
@@ -0,0 +1,214 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, PlaybackMetricCreate, VideoResolution } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test metrics API validators', function () {
let server: PeerTubeServer
let videoUUID: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1, {
open_telemetry: {
metrics: {
enabled: true
}
}
})
await setAccessTokensToServers([ server ])
const { uuid } = await server.videos.quickUpload({ name: 'video' })
videoUUID = uuid
})
describe('When adding playback metrics', function () {
const path = '/api/v1/metrics/playback'
let baseParams: PlaybackMetricCreate
before(function () {
baseParams = {
playerMode: 'p2p-media-loader',
resolution: VideoResolution.H_1080P,
fps: 30,
resolutionChanges: 1,
errors: 2,
p2pEnabled: true,
downloadedBytesP2P: 0,
downloadedBytesHTTP: 0,
uploadedBytesP2P: 0,
videoId: videoUUID
}
})
it('Should fail with an invalid resolution', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, resolution: 'toto' }
})
})
it('Should fail with an invalid fps', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, fps: 'toto' }
})
})
it('Should fail with a missing/invalid player mode', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'playerMode' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, playerMode: 'toto' }
})
})
it('Should fail with an missing/invalid resolution changes', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'resolutionChanges' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, resolutionChanges: 'toto' }
})
})
it('Should fail with an missing/invalid errors', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'errors' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, errors: 'toto' }
})
})
it('Should fail with an missing/invalid downloadedBytesP2P', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'downloadedBytesP2P' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, downloadedBytesP2P: 'toto' }
})
})
it('Should fail with an missing/invalid downloadedBytesHTTP', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'downloadedBytesHTTP' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, downloadedBytesHTTP: 'toto' }
})
})
it('Should fail with an missing/invalid uploadedBytesP2P', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'uploadedBytesP2P' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, uploadedBytesP2P: 'toto' }
})
})
it('Should fail with a missing/invalid p2pEnabled', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'p2pEnabled' ])
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, p2pEnabled: 'toto' }
})
})
it('Should fail with an invalid totalPeers', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, p2pPeers: 'toto' }
})
})
it('Should fail with a bad video id', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, videoId: 'toto' }
})
})
it('Should fail with an unknown video', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, videoId: 42 },
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: baseParams,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, p2pEnabled: false, totalPeers: 32 },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+492
ファイルの表示
@@ -0,0 +1,492 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import { HttpStatusCode, UserRole, VideoCreateResult } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makePutBodyRequest,
makeUploadRequest,
PeerTubeServer,
setAccessTokensToServers,
UsersCommand
} from '@peertube/peertube-server-commands'
describe('Test my user API validators', function () {
const path = '/api/v1/users/'
let userId: number
let rootId: number
let moderatorId: number
let video: VideoCreateResult
let server: PeerTubeServer
let userToken = ''
let moderatorToken = ''
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
{
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
}
{
const result = await server.users.generate('user1')
userToken = result.token
userId = result.userId
}
{
const result = await server.users.generate('moderator1', UserRole.MODERATOR)
moderatorToken = result.token
}
{
const result = await server.users.generate('moderator2', UserRole.MODERATOR)
moderatorId = result.userId
}
{
video = await server.videos.upload()
}
})
describe('When updating my account', function () {
it('Should fail with an invalid email attribute', async function () {
const fields = {
email: 'blabla'
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields })
})
it('Should fail with a too small password', async function () {
const fields = {
currentPassword: 'password',
password: 'bla'
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with a too long password', async function () {
const fields = {
currentPassword: 'password',
password: 'super'.repeat(61)
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail without the current password', async function () {
const fields = {
currentPassword: 'password',
password: 'super'.repeat(61)
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an invalid current password', async function () {
const fields = {
currentPassword: 'my super password fail',
password: 'super'.repeat(61)
}
await makePutBodyRequest({
url: server.url,
path: path + 'me',
token: userToken,
fields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with an invalid NSFW policy attribute', async function () {
const fields = {
nsfwPolicy: 'hello'
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an invalid autoPlayVideo attribute', async function () {
const fields = {
autoPlayVideo: -1
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an invalid autoPlayNextVideo attribute', async function () {
const fields = {
autoPlayNextVideo: -1
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
const fields = {
videosHistoryEnabled: -1
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an non authenticated user', async function () {
const fields = {
currentPassword: 'password',
password: 'my super password'
}
await makePutBodyRequest({
url: server.url,
path: path + 'me',
token: 'supertoken',
fields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a too long description', async function () {
const fields = {
description: 'super'.repeat(201)
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an invalid videoLanguages attribute', async function () {
{
const fields = {
videoLanguages: 'toto'
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
}
{
const languages = []
for (let i = 0; i < 1000; i++) {
languages.push('fr')
}
const fields = {
videoLanguages: languages
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
}
})
it('Should fail with an invalid theme', async function () {
const fields = { theme: 'invalid' }
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with an unknown theme', async function () {
const fields = { theme: 'peertube-theme-unknown' }
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
})
it('Should fail with invalid no modal attributes', async function () {
const keys = [
'noInstanceConfigWarningModal',
'noAccountSetupWarningModal',
'noWelcomeModal'
]
for (const key of keys) {
const fields = {
[key]: -1
}
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
}
})
it('Should succeed to change password with the correct params', async function () {
const fields = {
currentPassword: 'password',
password: 'my super password',
nsfwPolicy: 'blur',
autoPlayVideo: false,
email: 'super_email@example.com',
theme: 'default',
noInstanceConfigWarningModal: true,
noWelcomeModal: true,
noAccountSetupWarningModal: true
}
await makePutBodyRequest({
url: server.url,
path: path + 'me',
token: userToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should succeed without password change with the correct params', async function () {
const fields = {
nsfwPolicy: 'blur',
autoPlayVideo: false
}
await makePutBodyRequest({
url: server.url,
path: path + 'me',
token: userToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When updating my avatar', function () {
it('Should fail without an incorrect input file', async function () {
const fields = {}
const attaches = {
avatarfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
})
it('Should fail with a big file', async function () {
const fields = {}
const attaches = {
avatarfile: buildAbsoluteFixturePath('avatar-big.png')
}
await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
})
it('Should fail with an unauthenticated user', async function () {
const fields = {}
const attaches = {
avatarfile: buildAbsoluteFixturePath('avatar.png')
}
await makeUploadRequest({
url: server.url,
path: path + '/me/avatar/pick',
fields,
attaches,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct params', async function () {
const fields = {}
const attaches = {
avatarfile: buildAbsoluteFixturePath('avatar.png')
}
await makeUploadRequest({
url: server.url,
path: path + '/me/avatar/pick',
token: server.accessToken,
fields,
attaches,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When managing my scoped tokens', function () {
it('Should fail to get my scoped tokens with an non authenticated user', async function () {
await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail to get my scoped tokens with a bad token', async function () {
await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should succeed to get my scoped tokens', async function () {
await server.users.getMyScopedTokens()
})
it('Should fail to renew my scoped tokens with an non authenticated user', async function () {
await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail to renew my scoped tokens with a bad token', async function () {
await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should succeed to renew my scoped tokens', async function () {
await server.users.renewMyScopedTokens()
})
})
describe('When getting my information', function () {
it('Should fail with a non authenticated user', async function () {
await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should success with the correct parameters', async function () {
await server.users.getMyInfo({ token: userToken })
})
})
describe('When getting my video rating', function () {
let command: UsersCommand
before(function () {
command = server.users
})
it('Should fail with a non authenticated user', async function () {
await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an incorrect video uuid', async function () {
await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown video', async function () {
await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct parameters', async function () {
await command.getMyRating({ videoId: video.id })
await command.getMyRating({ videoId: video.uuid })
await command.getMyRating({ videoId: video.shortUUID })
})
})
describe('When retrieving my global ratings', function () {
const path = '/api/v1/accounts/user1/ratings'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, userToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, userToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, userToken)
})
it('Should fail with a unauthenticated user', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a another user', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad type', async function () {
await makeGetRequest({
url: server.url,
path,
token: userToken,
query: { rating: 'toto ' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When getting my global followers', function () {
const path = '/api/v1/accounts/user1/followers'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, userToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, userToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, userToken)
})
it('Should fail with a unauthenticated user', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a another user', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When blocking/unblocking/removing user', function () {
it('Should fail with an incorrect id', async function () {
const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await server.users.remove(options)
await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with the root user', async function () {
const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await server.users.remove(options)
await server.users.banUser(options)
await server.users.unbanUser(options)
})
it('Should return 404 with a non existing id', async function () {
const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await server.users.remove(options)
await server.users.banUser(options)
await server.users.unbanUser(options)
})
it('Should fail with a non admin user', async function () {
const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
await server.users.remove(options)
await server.users.banUser(options)
await server.users.unbanUser(options)
})
it('Should fail on a moderator with a moderator', async function () {
const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
await server.users.remove(options)
await server.users.banUser(options)
await server.users.unbanUser(options)
})
it('Should succeed on a user with a moderator', async function () {
const options = { userId, token: moderatorToken }
await server.users.banUser(options)
await server.users.unbanUser(options)
})
})
describe('When deleting our account', function () {
it('Should fail with with the root account', async function () {
await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests([ server ])
})
})
+490
ファイルの表示
@@ -0,0 +1,490 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode, PeerTubePlugin, PluginType } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test server plugins API validators', function () {
let server: PeerTubeServer
let userAccessToken = null
const npmPlugin = 'peertube-plugin-hello-world'
const pluginName = 'hello-world'
let npmVersion: string
const themePlugin = 'peertube-theme-background-red'
const themeName = 'background-red'
let themeVersion: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = {
username: 'user1',
password: 'password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
{
const res = await server.plugins.install({ npmName: npmPlugin })
const plugin = res.body as PeerTubePlugin
npmVersion = plugin.version
}
{
const res = await server.plugins.install({ npmName: themePlugin })
const plugin = res.body as PeerTubePlugin
themeVersion = plugin.version
}
})
describe('With static plugin routes', function () {
it('Should fail with an unknown plugin name/plugin version', async function () {
const paths = [
'/plugins/' + pluginName + '/0.0.1/auth/fake-auth',
'/plugins/' + pluginName + '/0.0.1/static/images/chocobo.png',
'/plugins/' + pluginName + '/0.0.1/client-scripts/client/common-client-plugin.js',
'/themes/' + themeName + '/0.0.1/static/images/chocobo.png',
'/themes/' + themeName + '/0.0.1/client-scripts/client/video-watch-client-plugin.js',
'/themes/' + themeName + '/0.0.1/css/assets/style1.css'
]
for (const p of paths) {
await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
})
it('Should fail when requesting a plugin in the theme path', async function () {
await makeGetRequest({
url: server.url,
path: '/themes/' + pluginName + '/' + npmVersion + '/static/images/chocobo.png',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with invalid versions', async function () {
const paths = [
'/plugins/' + pluginName + '/0.0.1.1/auth/fake-auth',
'/plugins/' + pluginName + '/0.0.1.1/static/images/chocobo.png',
'/plugins/' + pluginName + '/0.1/client-scripts/client/common-client-plugin.js',
'/themes/' + themeName + '/1/static/images/chocobo.png',
'/themes/' + themeName + '/0.0.1000a/client-scripts/client/video-watch-client-plugin.js',
'/themes/' + themeName + '/0.a.1/css/assets/style1.css'
]
for (const p of paths) {
await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
})
it('Should fail with invalid paths', async function () {
const paths = [
'/plugins/' + pluginName + '/' + npmVersion + '/static/images/../chocobo.png',
'/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/h/o/../client/common-client-plugin.js',
'/themes/' + themeName + '/' + themeVersion + '/static/hola/a/../images/chocobo.png',
'/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/video-watch-client-plugin.js/..',
'/themes/' + themeName + '/' + themeVersion + '/css/hiha//j../assets/style1.css'
]
for (const p of paths) {
await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
})
it('Should fail with an unknown auth name', async function () {
const path = '/plugins/' + pluginName + '/' + npmVersion + '/auth/bad-auth'
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an unknown static file', async function () {
const paths = [
'/plugins/' + pluginName + '/' + npmVersion + '/static/fake/chocobo.png',
'/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/client/fake.js',
'/themes/' + themeName + '/' + themeVersion + '/static/fake/chocobo.png',
'/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/fake.js'
]
for (const p of paths) {
await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
})
it('Should fail with an unknown CSS file', async function () {
await makeGetRequest({
url: server.url,
path: '/themes/' + themeName + '/' + themeVersion + '/css/assets/fake.css',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
const paths = [
'/plugins/' + pluginName + '/' + npmVersion + '/static/images/chocobo.png',
'/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/client/common-client-plugin.js',
'/themes/' + themeName + '/' + themeVersion + '/static/images/chocobo.png',
'/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/video-watch-client-plugin.js',
'/themes/' + themeName + '/' + themeVersion + '/css/assets/style1.css'
]
for (const p of paths) {
await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.OK_200 })
}
const authPath = '/plugins/' + pluginName + '/' + npmVersion + '/auth/fake-auth'
await makeGetRequest({ url: server.url, path: authPath, expectedStatus: HttpStatusCode.FOUND_302 })
})
})
describe('When listing available plugins/themes', function () {
const path = '/api/v1/plugins/available'
const baseQuery = {
search: 'super search',
pluginType: PluginType.PLUGIN,
currentPeerTubeEngine: '1.2.3'
}
it('Should fail with an invalid token', async function () {
await makeGetRequest({
url: server.url,
path,
token: 'fake_token',
query: baseQuery,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
query: baseQuery,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an invalid plugin type', async function () {
const query = { ...baseQuery, pluginType: 5 }
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query
})
})
it('Should fail with an invalid current peertube engine', async function () {
const query = { ...baseQuery, currentPeerTubeEngine: '1.0' }
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: baseQuery,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When listing local plugins/themes', function () {
const path = '/api/v1/plugins'
const baseQuery = {
pluginType: PluginType.THEME
}
it('Should fail with an invalid token', async function () {
await makeGetRequest({
url: server.url,
path,
token: 'fake_token',
query: baseQuery,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
query: baseQuery,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an invalid plugin type', async function () {
const query = { ...baseQuery, pluginType: 5 }
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
query: baseQuery,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When getting a plugin or the registered settings or public settings', function () {
const path = '/api/v1/plugins/'
it('Should fail with an invalid token', async function () {
for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings` ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should fail if the user is not an administrator', async function () {
for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings` ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should fail with an invalid npm name', async function () {
for (const suffix of [ 'toto', 'toto/registered-settings', 'toto/public-settings' ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
for (const suffix of [ 'peertube-plugin-TOTO', 'peertube-plugin-TOTO/registered-settings', 'peertube-plugin-TOTO/public-settings' ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
})
it('Should fail with an unknown plugin', async function () {
for (const suffix of [ 'peertube-plugin-toto', 'peertube-plugin-toto/registered-settings', 'peertube-plugin-toto/public-settings' ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
}
})
it('Should succeed with the correct parameters', async function () {
for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings`, `${npmPlugin}/public-settings` ]) {
await makeGetRequest({
url: server.url,
path: path + suffix,
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
}
})
})
describe('When updating plugin settings', function () {
const path = '/api/v1/plugins/'
const settings = { setting1: 'value1' }
it('Should fail with an invalid token', async function () {
await makePutBodyRequest({
url: server.url,
path: path + npmPlugin + '/settings',
fields: { settings },
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePutBodyRequest({
url: server.url,
path: path + npmPlugin + '/settings',
fields: { settings },
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid npm name', async function () {
await makePutBodyRequest({
url: server.url,
path: path + 'toto/settings',
fields: { settings },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makePutBodyRequest({
url: server.url,
path: path + 'peertube-plugin-TOTO/settings',
fields: { settings },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown plugin', async function () {
await makePutBodyRequest({
url: server.url,
path: path + 'peertube-plugin-toto/settings',
fields: { settings },
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: server.url,
path: path + npmPlugin + '/settings',
fields: { settings },
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When installing/updating/uninstalling a plugin', function () {
const path = '/api/v1/plugins/'
it('Should fail with an invalid token', async function () {
for (const suffix of [ 'install', 'update', 'uninstall' ]) {
await makePostBodyRequest({
url: server.url,
path: path + suffix,
fields: { npmName: npmPlugin },
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should fail if the user is not an administrator', async function () {
for (const suffix of [ 'install', 'update', 'uninstall' ]) {
await makePostBodyRequest({
url: server.url,
path: path + suffix,
fields: { npmName: npmPlugin },
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should fail with an invalid npm name', async function () {
for (const suffix of [ 'install', 'update', 'uninstall' ]) {
await makePostBodyRequest({
url: server.url,
path: path + suffix,
fields: { npmName: 'toto' },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
for (const suffix of [ 'install', 'update', 'uninstall' ]) {
await makePostBodyRequest({
url: server.url,
path: path + suffix,
fields: { npmName: 'peertube-plugin-TOTO' },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
})
it('Should succeed with the correct parameters', async function () {
const it = [
{ suffix: 'install', status: HttpStatusCode.OK_200 },
{ suffix: 'update', status: HttpStatusCode.OK_200 },
{ suffix: 'uninstall', status: HttpStatusCode.NO_CONTENT_204 }
]
for (const obj of it) {
await makePostBodyRequest({
url: server.url,
path: path + obj.suffix,
fields: { npmName: npmPlugin },
token: server.accessToken,
expectedStatus: obj.status
})
}
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+240
ファイルの表示
@@ -0,0 +1,240 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode, VideoCreateResult } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test server redundancy API validators', function () {
let servers: PeerTubeServer[]
let userAccessToken = null
let videoIdLocal: number
let videoRemote: VideoCreateResult
// ---------------------------------------------------------------
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
const user = {
username: 'user1',
password: 'password'
}
await servers[0].users.create({ username: user.username, password: user.password })
userAccessToken = await servers[0].login.getAccessToken(user)
videoIdLocal = (await servers[0].videos.quickUpload({ name: 'video' })).id
const remoteUUID = (await servers[1].videos.quickUpload({ name: 'video' })).uuid
await waitJobs(servers)
videoRemote = await servers[0].videos.get({ id: remoteUUID })
})
describe('When listing redundancies', function () {
const path = '/api/v1/server/redundancy/videos'
let url: string
let token: string
before(function () {
url = servers[0].url
token = servers[0].accessToken
})
it('Should fail with an invalid token', async function () {
await makeGetRequest({ url, path, token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail if the user is not an administrator', async function () {
await makeGetRequest({ url, path, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(url, path, servers[0].accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(url, path, servers[0].accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(url, path, servers[0].accessToken)
})
it('Should fail with a bad target', async function () {
await makeGetRequest({ url, path, token, query: { target: 'bad target' } })
})
it('Should fail without target', async function () {
await makeGetRequest({ url, path, token })
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When manually adding a redundancy', function () {
const path = '/api/v1/server/redundancy/videos'
let url: string
let token: string
before(function () {
url = servers[0].url
token = servers[0].accessToken
})
it('Should fail with an invalid token', async function () {
await makePostBodyRequest({ url, path, token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail if the user is not an administrator', async function () {
await makePostBodyRequest({ url, path, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail without a video id', async function () {
await makePostBodyRequest({ url, path, token })
})
it('Should fail with an incorrect video id', async function () {
await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } })
})
it('Should fail with a not found video id', async function () {
await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a local a video id', async function () {
await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } })
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url,
path,
token,
fields: { videoId: videoRemote.shortUUID },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should fail if the video is already duplicated', async function () {
this.timeout(30000)
await waitJobs(servers)
await makePostBodyRequest({
url,
path,
token,
fields: { videoId: videoRemote.uuid },
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
})
describe('When manually removing a redundancy', function () {
const path = '/api/v1/server/redundancy/videos/'
let url: string
let token: string
before(function () {
url = servers[0].url
token = servers[0].accessToken
})
it('Should fail with an invalid token', async function () {
await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail if the user is not an administrator', async function () {
await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an incorrect video id', async function () {
await makeDeleteRequest({ url, path: path + 'toto', token })
})
it('Should fail with a not found video redundancy', async function () {
await makeDeleteRequest({ url, path: path + '454545', token, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('When updating server redundancy', function () {
const path = '/api/v1/server/redundancy'
it('Should fail with an invalid token', async function () {
await makePutBodyRequest({
url: servers[0].url,
path: path + '/' + servers[1].host,
fields: { redundancyAllowed: true },
token: 'fake_token',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if the user is not an administrator', async function () {
await makePutBodyRequest({
url: servers[0].url,
path: path + '/' + servers[1].host,
fields: { redundancyAllowed: true },
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail if we do not follow this server', async function () {
await makePutBodyRequest({
url: servers[0].url,
path: path + '/example.com',
fields: { redundancyAllowed: true },
token: servers[0].accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail without de redundancyAllowed param', async function () {
await makePutBodyRequest({
url: servers[0].url,
path: path + '/' + servers[1].host,
fields: { blabla: true },
token: servers[0].accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: servers[0].url,
path: path + '/' + servers[1].host,
fields: { redundancyAllowed: true },
token: servers[0].accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests(servers)
})
})
+446
ファイルの表示
@@ -0,0 +1,446 @@
import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, HttpStatusCodeType, UserRole } from '@peertube/peertube-models'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import {
cleanupTests,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultAccountAvatar,
setDefaultChannelAvatar
} from '@peertube/peertube-server-commands'
describe('Test registrations API validators', function () {
let server: PeerTubeServer
let userToken: string
let moderatorToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultAccountAvatar([ server ])
await setDefaultChannelAvatar([ server ])
await server.config.enableSignup(false);
({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR));
({ token: userToken } = await server.users.generate('user', UserRole.USER))
})
describe('Register', function () {
const registrationPath = '/api/v1/users/register'
const registrationRequestPath = '/api/v1/users/registrations/request'
const baseCorrectParams = {
username: 'user3',
displayName: 'super user',
email: 'test3@example.com',
password: 'my super password',
registrationReason: 'my super registration reason'
}
describe('When registering a new user or requesting user registration', function () {
async function check (fields: any, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
await server.config.enableSignup(false)
await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus })
await server.config.enableSignup(true)
await makePostBodyRequest({ url: server.url, path: registrationRequestPath, fields, expectedStatus })
}
it('Should fail with a too small username', async function () {
const fields = { ...baseCorrectParams, username: '' }
await check(fields)
})
it('Should fail with a too long username', async function () {
const fields = { ...baseCorrectParams, username: 'super'.repeat(50) }
await check(fields)
})
it('Should fail with an incorrect username', async function () {
const fields = { ...baseCorrectParams, username: 'my username' }
await check(fields)
})
it('Should fail with a missing email', async function () {
const fields = omit(baseCorrectParams, [ 'email' ])
await check(fields)
})
it('Should fail with an invalid email', async function () {
const fields = { ...baseCorrectParams, email: 'test_example.com' }
await check(fields)
})
it('Should fail with a too small password', async function () {
const fields = { ...baseCorrectParams, password: 'bla' }
await check(fields)
})
it('Should fail with a too long password', async function () {
const fields = { ...baseCorrectParams, password: 'super'.repeat(61) }
await check(fields)
})
it('Should fail if we register a user with the same username', async function () {
const fields = { ...baseCorrectParams, username: 'root' }
await check(fields, HttpStatusCode.CONFLICT_409)
})
it('Should fail with a "peertube" username', async function () {
const fields = { ...baseCorrectParams, username: 'peertube' }
await check(fields, HttpStatusCode.CONFLICT_409)
})
it('Should fail if we register a user with the same email', async function () {
const fields = { ...baseCorrectParams, email: 'admin' + server.internalServerNumber + '@example.com' }
await check(fields, HttpStatusCode.CONFLICT_409)
})
it('Should fail with a bad display name', async function () {
const fields = { ...baseCorrectParams, displayName: 'a'.repeat(150) }
await check(fields)
})
it('Should fail with a bad channel name', async function () {
const fields = { ...baseCorrectParams, channel: { name: '[]azf', displayName: 'toto' } }
await check(fields)
})
it('Should fail with a bad channel display name', async function () {
const fields = { ...baseCorrectParams, channel: { name: 'toto', displayName: '' } }
await check(fields)
})
it('Should fail with a channel name that is the same as username', async function () {
const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
const fields = { ...baseCorrectParams, ...source }
await check(fields)
})
it('Should fail with an existing channel', async function () {
const attributes = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
await server.channels.create({ attributes })
const fields = { ...baseCorrectParams, channel: { name: 'existing_channel', displayName: 'toto' } }
await check(fields, HttpStatusCode.CONFLICT_409)
})
it('Should fail on a server with registration disabled', async function () {
this.timeout(60000)
await server.config.updateExistingConfig({
newConfig: {
signup: {
enabled: false
}
}
})
await server.registrations.register({ username: 'user4', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await server.registrations.requestRegistration({
username: 'user4',
registrationReason: 'reason',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail if the user limit is reached', async function () {
this.timeout(60000)
const { total } = await server.users.list()
await server.config.enableSignup(false, total)
await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await server.config.enableSignup(true, total)
await server.registrations.requestRegistration({
username: 'user42',
registrationReason: 'reason',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed if the user limit is not reached', async function () {
this.timeout(60000)
const { total } = await server.users.list()
await server.config.enableSignup(false, total + 1)
await server.registrations.register({ username: 'user43', expectedStatus: HttpStatusCode.NO_CONTENT_204 })
await server.config.enableSignup(true, total + 2)
await server.registrations.requestRegistration({
username: 'user44',
registrationReason: 'reason',
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('On direct registration', function () {
it('Should succeed with the correct params', async function () {
await server.config.enableSignup(false)
const fields = {
username: 'user_direct_1',
displayName: 'super user direct 1',
email: 'user_direct_1@example.com',
password: 'my super password',
channel: { name: 'super_user_direct_1_channel', displayName: 'super user direct 1 channel' }
}
await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
})
it('Should fail if the instance requires approval', async function () {
this.timeout(60000)
await server.config.enableSignup(true)
await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
})
describe('On registration request', function () {
before(async function () {
this.timeout(60000)
await server.config.enableSignup(true)
})
it('Should fail with an invalid registration reason', async function () {
for (const registrationReason of [ '', 't', 't'.repeat(5000) ]) {
await server.registrations.requestRegistration({
username: 'user_request_1',
registrationReason,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
})
it('Should succeed with the correct params', async function () {
await server.registrations.requestRegistration({
username: 'user_request_2',
registrationReason: 'tt',
channel: {
displayName: 'my user request 2 channel',
name: 'user_request_2_channel'
}
})
})
it('Should fail if the username is already awaiting registration approval', async function () {
await server.registrations.requestRegistration({
username: 'user_request_2',
registrationReason: 'tt',
channel: {
displayName: 'my user request 42 channel',
name: 'user_request_42_channel'
},
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail if the email is already awaiting registration approval', async function () {
await server.registrations.requestRegistration({
username: 'user42',
email: 'user_request_2@example.com',
registrationReason: 'tt',
channel: {
displayName: 'my user request 42 channel',
name: 'user_request_42_channel'
},
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail if the channel is already awaiting registration approval', async function () {
await server.registrations.requestRegistration({
username: 'user42',
registrationReason: 'tt',
channel: {
displayName: 'my user request 2 channel',
name: 'user_request_2_channel'
},
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail if the instance does not require approval', async function () {
this.timeout(60000)
await server.config.enableSignup(false)
await server.registrations.requestRegistration({
username: 'user42',
registrationReason: 'toto',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
})
describe('Registrations accept/reject', function () {
let id1: number
let id2: number
before(async function () {
this.timeout(60000)
await server.config.enableSignup(true);
({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' }));
({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' }))
})
it('Should fail to accept/reject registration without token', async function () {
const options = { id: id1, moderationResponse: 'tt', token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }
await server.registrations.accept(options)
await server.registrations.reject(options)
})
it('Should fail to accept/reject registration with a non moderator user', async function () {
const options = { id: id1, moderationResponse: 'tt', token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
await server.registrations.accept(options)
await server.registrations.reject(options)
})
it('Should fail to accept/reject registration with a bad registration id', async function () {
{
const options = { id: 't' as any, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await server.registrations.accept(options)
await server.registrations.reject(options)
}
{
const options = { id: 42, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await server.registrations.accept(options)
await server.registrations.reject(options)
}
})
it('Should fail to accept/reject registration with a bad moderation resposne', async function () {
for (const moderationResponse of [ '', 't', 't'.repeat(5000) ]) {
const options = { id: id1, moderationResponse, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await server.registrations.accept(options)
await server.registrations.reject(options)
}
})
it('Should succeed to accept a registration', async function () {
await server.registrations.accept({ id: id1, moderationResponse: 'tt', token: moderatorToken })
})
it('Should succeed to reject a registration', async function () {
await server.registrations.reject({ id: id2, moderationResponse: 'tt', token: moderatorToken })
})
it('Should fail to accept/reject a registration that was already accepted/rejected', async function () {
for (const id of [ id1, id2 ]) {
const options = { id, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.CONFLICT_409 }
await server.registrations.accept(options)
await server.registrations.reject(options)
}
})
})
describe('Registrations deletion', function () {
let id1: number
let id2: number
let id3: number
before(async function () {
({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' }));
({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' }));
({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' }))
await server.registrations.accept({ id: id2, moderationResponse: 'tt' })
await server.registrations.reject({ id: id3, moderationResponse: 'tt' })
})
it('Should fail to delete registration without token', async function () {
await server.registrations.delete({ id: id1, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail to delete registration with a non moderator user', async function () {
await server.registrations.delete({ id: id1, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail to delete registration with a bad registration id', async function () {
await server.registrations.delete({ id: 't' as any, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.registrations.delete({ id: 42, token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.registrations.delete({ id: id1, token: moderatorToken })
await server.registrations.delete({ id: id2, token: moderatorToken })
await server.registrations.delete({ id: id3, token: moderatorToken })
})
})
describe('Listing registrations', function () {
const path = '/api/v1/users/registrations'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await server.registrations.list({
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await server.registrations.list({
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await server.registrations.list({
token: moderatorToken,
search: 'toto'
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+911
ファイルの表示
@@ -0,0 +1,911 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { basename } from 'path'
import {
HttpStatusCode,
HttpStatusCodeType,
isVideoStudioTaskIntro,
RunnerJob,
RunnerJobState,
RunnerJobStudioTranscodingPayload,
RunnerJobSuccessPayload,
RunnerJobUpdatePayload,
VideoPrivacy,
VideoStudioTaskIntro
} from '@peertube/peertube-models'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import {
cleanupTests,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
sendRTMPStream,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
VideoStudioCommand,
waitJobs
} from '@peertube/peertube-server-commands'
const badUUID = '910ec12a-d9e6-458b-a274-0abb655f9464'
describe('Test managing runners', function () {
let server: PeerTubeServer
let userToken: string
let registrationTokenId: number
let registrationToken: string
let runnerToken: string
let runnerToken2: string
let completedJobToken: string
let completedJobUUID: string
let cancelledJobToken: string
let cancelledJobUUID: string
before(async function () {
this.timeout(120000)
const config = {
rates_limit: {
api: {
max: 5000
}
}
}
server = await createSingleServer(1, config)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
userToken = await server.users.generateUserAndToken('user1')
const { data } = await server.runnerRegistrationTokens.list()
registrationToken = data[0].registrationToken
registrationTokenId = data[0].id
await server.config.enableTranscoding({ hls: true, webVideo: true })
await server.config.enableStudio()
await server.config.enableRemoteTranscoding()
await server.config.enableRemoteStudio()
runnerToken = await server.runners.autoRegisterRunner()
runnerToken2 = await server.runners.autoRegisterRunner()
{
await server.videos.quickUpload({ name: 'video 1' })
await server.videos.quickUpload({ name: 'video 2' })
await waitJobs([ server ])
{
const job = await server.runnerJobs.autoProcessWebVideoJob(runnerToken)
completedJobToken = job.jobToken
completedJobUUID = job.uuid
}
{
const { job } = await server.runnerJobs.autoAccept({ runnerToken })
cancelledJobToken = job.jobToken
cancelledJobUUID = job.uuid
await server.runnerJobs.cancelByAdmin({ jobUUID: cancelledJobUUID })
}
}
})
describe('Managing runner registration tokens', function () {
describe('Common', function () {
it('Should fail to generate, list or delete runner registration token without oauth token', async function () {
const expectedStatus = HttpStatusCode.UNAUTHORIZED_401
await server.runnerRegistrationTokens.generate({ token: null, expectedStatus })
await server.runnerRegistrationTokens.list({ token: null, expectedStatus })
await server.runnerRegistrationTokens.delete({ token: null, id: registrationTokenId, expectedStatus })
})
it('Should fail to generate, list or delete runner registration token without admin rights', async function () {
const expectedStatus = HttpStatusCode.FORBIDDEN_403
await server.runnerRegistrationTokens.generate({ token: userToken, expectedStatus })
await server.runnerRegistrationTokens.list({ token: userToken, expectedStatus })
await server.runnerRegistrationTokens.delete({ token: userToken, id: registrationTokenId, expectedStatus })
})
})
describe('Delete', function () {
it('Should fail to delete with a bad id', async function () {
await server.runnerRegistrationTokens.delete({ id: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('List', function () {
const path = '/api/v1/runners/registration-tokens'
it('Should fail to list with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail to list with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail to list with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should succeed to list with the correct params', async function () {
await server.runnerRegistrationTokens.list({ start: 0, count: 5, sort: '-createdAt' })
})
})
})
describe('Managing runners', function () {
let toDeleteId: number
describe('Register', function () {
const name = 'runner name'
it('Should fail with a bad registration token', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await server.runners.register({ name, registrationToken: 'a'.repeat(4000), expectedStatus })
await server.runners.register({ name, registrationToken: null, expectedStatus })
})
it('Should fail with an unknown registration token', async function () {
await server.runners.register({ name, registrationToken: 'aaa', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a bad name', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await server.runners.register({ name: '', registrationToken, expectedStatus })
await server.runners.register({ name: 'a'.repeat(200), registrationToken, expectedStatus })
})
it('Should fail with an invalid description', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await server.runners.register({ name, description: '', registrationToken, expectedStatus })
await server.runners.register({ name, description: 'a'.repeat(5000), registrationToken, expectedStatus })
})
it('Should succeed with the correct params', async function () {
const { id } = await server.runners.register({ name, description: 'super description', registrationToken })
toDeleteId = id
})
it('Should fail with the same runner name', async function () {
await server.runners.register({
name,
description: 'super description',
registrationToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('Delete', function () {
it('Should fail without oauth token', async function () {
await server.runners.delete({ token: null, id: toDeleteId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without admin rights', async function () {
await server.runners.delete({ token: userToken, id: toDeleteId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad id', async function () {
await server.runners.delete({ id: 'hi' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown id', async function () {
await server.runners.delete({ id: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.runners.delete({ id: toDeleteId })
})
})
describe('List', function () {
const path = '/api/v1/runners'
it('Should fail without oauth token', async function () {
await server.runners.list({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without admin rights', async function () {
await server.runners.list({ token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail to list with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail to list with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail to list with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an invalid state', async function () {
await server.runners.list({ start: 0, count: 5, sort: '-createdAt' })
})
it('Should succeed to list with the correct params', async function () {
await server.runners.list({ start: 0, count: 5, sort: '-createdAt' })
})
})
})
describe('Runner jobs by admin', function () {
describe('Cancel', function () {
let jobUUID: string
before(async function () {
this.timeout(60000)
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
jobUUID = availableJobs[0].uuid
})
it('Should fail without oauth token', async function () {
await server.runnerJobs.cancelByAdmin({ token: null, jobUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without admin rights', async function () {
await server.runnerJobs.cancelByAdmin({ token: userToken, jobUUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad job uuid', async function () {
await server.runnerJobs.cancelByAdmin({ jobUUID: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown job uuid', async function () {
const jobUUID = badUUID
await server.runnerJobs.cancelByAdmin({ jobUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an already cancelled job', async function () {
await server.runnerJobs.cancelByAdmin({ jobUUID: cancelledJobUUID, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct params', async function () {
await server.runnerJobs.cancelByAdmin({ jobUUID })
})
})
describe('List', function () {
const path = '/api/v1/runners/jobs'
it('Should fail without oauth token', async function () {
await server.runnerJobs.list({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without admin rights', async function () {
await server.runnerJobs.list({ token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail to list with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail to list with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail to list with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an invalid state', async function () {
await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: 42 as any })
await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: [ 42 ] as any })
})
it('Should succeed with the correct params', async function () {
await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: [ RunnerJobState.COMPLETED ] })
})
})
describe('Delete', function () {
let jobUUID: string
before(async function () {
this.timeout(60000)
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
jobUUID = availableJobs[0].uuid
})
it('Should fail without oauth token', async function () {
await server.runnerJobs.deleteByAdmin({ token: null, jobUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail without admin rights', async function () {
await server.runnerJobs.deleteByAdmin({ token: userToken, jobUUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad job uuid', async function () {
await server.runnerJobs.deleteByAdmin({ jobUUID: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown job uuid', async function () {
const jobUUID = badUUID
await server.runnerJobs.deleteByAdmin({ jobUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.runnerJobs.deleteByAdmin({ jobUUID })
})
})
})
describe('Runner jobs by runners', function () {
let jobUUID: string
let jobToken: string
let videoUUID: string
let jobUUID2: string
let jobToken2: string
let videoUUID2: string
let pendingUUID: string
let videoStudioUUID: string
let studioFile: string
let liveAcceptedJob: RunnerJob & { jobToken: string }
let studioAcceptedJob: RunnerJob & { jobToken: string }
async function fetchVideoInputFiles (options: {
jobUUID: string
videoUUID: string
runnerToken: string
jobToken: string
expectedStatus: HttpStatusCodeType
}) {
const { jobUUID, expectedStatus, videoUUID, runnerToken, jobToken } = options
const basePath = '/api/v1/runners/jobs/' + jobUUID + '/files/videos/' + videoUUID
const paths = [ `${basePath}/max-quality`, `${basePath}/previews/max-quality` ]
for (const path of paths) {
await makePostBodyRequest({ url: server.url, path, fields: { runnerToken, jobToken }, expectedStatus })
}
}
async function fetchStudioFiles (options: {
jobUUID: string
videoUUID: string
runnerToken: string
jobToken: string
studioFile?: string
expectedStatus: HttpStatusCodeType
}) {
const { jobUUID, expectedStatus, videoUUID, runnerToken, jobToken, studioFile } = options
const path = `/api/v1/runners/jobs/${jobUUID}/files/videos/${videoUUID}/studio/task-files/${studioFile}`
await makePostBodyRequest({ url: server.url, path, fields: { runnerToken, jobToken }, expectedStatus })
}
before(async function () {
this.timeout(120000)
{
await server.runnerJobs.cancelAllJobs({ state: RunnerJobState.PENDING })
}
{
const { uuid } = await server.videos.quickUpload({ name: 'video' })
videoUUID = uuid
await waitJobs([ server ])
const { job } = await server.runnerJobs.autoAccept({ runnerToken })
jobUUID = job.uuid
jobToken = job.jobToken
}
{
const { uuid } = await server.videos.quickUpload({ name: 'video' })
videoUUID2 = uuid
await waitJobs([ server ])
const { job } = await server.runnerJobs.autoAccept({ runnerToken: runnerToken2 })
jobUUID2 = job.uuid
jobToken2 = job.jobToken
}
{
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
pendingUUID = availableJobs[0].uuid
}
{
await server.config.disableTranscoding()
const { uuid } = await server.videos.quickUpload({ name: 'video studio' })
videoStudioUUID = uuid
await server.config.enableTranscoding({ hls: true, webVideo: true })
await server.config.enableStudio()
await server.videoStudio.createEditionTasks({
videoId: videoStudioUUID,
tasks: VideoStudioCommand.getComplexTask()
})
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'video-studio-transcoding' })
studioAcceptedJob = job
const tasks = (job.payload as RunnerJobStudioTranscodingPayload).tasks
const fileUrl = (tasks.find(t => isVideoStudioTaskIntro(t)) as VideoStudioTaskIntro).options.file as string
studioFile = basename(fileUrl)
}
{
await server.config.enableLive({
allowReplay: false,
resolutions: 'max',
transcoding: true
})
const { live } = await server.live.quickCreate({ permanentLive: true, saveReplay: false, privacy: VideoPrivacy.PUBLIC })
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await waitJobs([ server ])
await server.runnerJobs.requestLiveJob(runnerToken)
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'live-rtmp-hls-transcoding' })
liveAcceptedJob = job
await stopFfmpeg(ffmpegCommand)
}
})
describe('Common runner tokens validations', function () {
async function testEndpoints (options: {
jobUUID: string
runnerToken: string
jobToken: string
expectedStatus: HttpStatusCodeType
}) {
await server.runnerJobs.abort({ ...options, reason: 'reason' })
await server.runnerJobs.update({ ...options })
await server.runnerJobs.error({ ...options, message: 'message' })
await server.runnerJobs.success({ ...options, payload: { videoFile: 'video_short.mp4' } })
}
it('Should fail with an invalid job uuid', async function () {
const options = { jobUUID: 'a', runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await testEndpoints({ ...options, jobToken })
await fetchVideoInputFiles({ ...options, videoUUID, jobToken })
await fetchStudioFiles({ ...options, videoUUID, jobToken: studioAcceptedJob.jobToken, studioFile })
})
it('Should fail with an unknown job uuid', async function () {
const options = { jobUUID: badUUID, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobToken })
await fetchVideoInputFiles({ ...options, videoUUID, jobToken })
await fetchStudioFiles({ ...options, jobToken: studioAcceptedJob.jobToken, videoUUID, studioFile })
})
it('Should fail with an invalid runner token', async function () {
const options = { runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await testEndpoints({ ...options, jobUUID, jobToken })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
await fetchStudioFiles({
...options,
jobToken: studioAcceptedJob.jobToken,
jobUUID: studioAcceptedJob.uuid,
videoUUID: videoStudioUUID,
studioFile
})
})
it('Should fail with an unknown runner token', async function () {
const options = { runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobUUID, jobToken })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
await fetchStudioFiles({
...options,
jobToken: studioAcceptedJob.jobToken,
jobUUID: studioAcceptedJob.uuid,
videoUUID: videoStudioUUID,
studioFile
})
})
it('Should fail with an invalid job token job uuid', async function () {
const options = { runnerToken, jobToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
await testEndpoints({ ...options, jobUUID })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
})
it('Should fail with an unknown job token job uuid', async function () {
const options = { runnerToken, jobToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobUUID })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
})
it('Should fail with a runner token not associated to this job', async function () {
const options = { runnerToken: runnerToken2, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobUUID, jobToken })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
await fetchStudioFiles({
...options,
jobToken: studioAcceptedJob.jobToken,
jobUUID: studioAcceptedJob.uuid,
videoUUID: videoStudioUUID,
studioFile
})
})
it('Should fail with a job uuid not associated to the job token', async function () {
{
const options = { jobUUID: jobUUID2, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobToken })
await fetchVideoInputFiles({ ...options, jobToken, videoUUID })
await fetchStudioFiles({ ...options, jobToken: studioAcceptedJob.jobToken, videoUUID: videoStudioUUID, studioFile })
}
{
const options = { runnerToken, jobToken: jobToken2, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
await testEndpoints({ ...options, jobUUID })
await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
}
})
})
describe('Unregister', function () {
it('Should fail without a runner token', async function () {
await server.runners.unregister({ runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad a runner token', async function () {
await server.runners.unregister({ runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown runner token', async function () {
await server.runners.unregister({ runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('Request', function () {
it('Should fail without a runner token', async function () {
await server.runnerJobs.request({ runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad a runner token', async function () {
await server.runnerJobs.request({ runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown runner token', async function () {
await server.runnerJobs.request({ runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('Accept', function () {
it('Should fail with a bad a job uuid', async function () {
await server.runnerJobs.accept({ jobUUID: '', runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown job uuid', async function () {
await server.runnerJobs.accept({ jobUUID: badUUID, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a job not in pending state', async function () {
await server.runnerJobs.accept({ jobUUID: completedJobUUID, runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.runnerJobs.accept({ jobUUID: cancelledJobUUID, runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail without a runner token', async function () {
await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad a runner token', async function () {
await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown runner token', async function () {
await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('Abort', function () {
it('Should fail without a reason', async function () {
await server.runnerJobs.abort({ jobUUID, jobToken, runnerToken, reason: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad reason', async function () {
const reason = 'reason'.repeat(5000)
await server.runnerJobs.abort({ jobUUID, jobToken, runnerToken, reason, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a job not in processing state', async function () {
await server.runnerJobs.abort({
jobUUID: completedJobUUID,
jobToken: completedJobToken,
runnerToken,
reason: 'reason',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('Update', function () {
describe('Common', function () {
it('Should fail with an invalid progress', async function () {
await server.runnerJobs.update({ jobUUID, jobToken, runnerToken, progress: 101, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a job not in processing state', async function () {
await server.runnerJobs.update({
jobUUID: cancelledJobUUID,
jobToken: cancelledJobToken,
runnerToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
})
describe('Live RTMP to HLS', function () {
const base: RunnerJobUpdatePayload = {
masterPlaylistFile: 'live/master.m3u8',
resolutionPlaylistFilename: '0.m3u8',
resolutionPlaylistFile: 'live/1.m3u8',
type: 'add-chunk',
videoChunkFile: 'live/1-000069.ts',
videoChunkFilename: '1-000068.ts'
}
function testUpdate (payload: RunnerJobUpdatePayload) {
return server.runnerJobs.update({
jobUUID: liveAcceptedJob.uuid,
jobToken: liveAcceptedJob.jobToken,
payload,
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
it('Should fail with an invalid resolutionPlaylistFilename', async function () {
await testUpdate({ ...base, resolutionPlaylistFilename: undefined })
await testUpdate({ ...base, resolutionPlaylistFilename: 'coucou/hello' })
await testUpdate({ ...base, resolutionPlaylistFilename: 'hello' })
})
it('Should fail with an invalid videoChunkFilename', async function () {
await testUpdate({ ...base, resolutionPlaylistFilename: undefined })
await testUpdate({ ...base, resolutionPlaylistFilename: 'coucou/hello' })
await testUpdate({ ...base, resolutionPlaylistFilename: 'hello' })
})
it('Should fail with an invalid type', async function () {
await testUpdate({ ...base, type: undefined })
await testUpdate({ ...base, type: 'toto' as any })
})
})
})
describe('Error', function () {
it('Should fail with a missing error message', async function () {
await server.runnerJobs.error({ jobUUID, jobToken, runnerToken, message: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an invalid error messgae', async function () {
const message = 'a'.repeat(6000)
await server.runnerJobs.error({ jobUUID, jobToken, runnerToken, message, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a job not in processing state', async function () {
await server.runnerJobs.error({
jobUUID: completedJobUUID,
jobToken: completedJobToken,
message: 'my message',
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('Success', function () {
let vodJobUUID: string
let vodJobToken: string
describe('Common', function () {
it('Should fail with a job not in processing state', async function () {
await server.runnerJobs.success({
jobUUID: completedJobUUID,
jobToken: completedJobToken,
payload: { videoFile: 'video_short.mp4' },
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('VOD', function () {
it('Should fail with an invalid vod web video payload', async function () {
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-web-video-transcoding' })
await server.runnerJobs.success({
jobUUID: job.uuid,
jobToken: job.jobToken,
payload: { hello: 'video_short.mp4' } as any,
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
vodJobUUID = job.uuid
vodJobToken = job.jobToken
})
it('Should fail with an invalid vod hls payload', async function () {
// To create HLS jobs
const payload: RunnerJobSuccessPayload = { videoFile: 'video_short.mp4' }
await server.runnerJobs.success({ runnerToken, jobUUID: vodJobUUID, jobToken: vodJobToken, payload })
await waitJobs([ server ])
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-hls-transcoding' })
await server.runnerJobs.success({
jobUUID: job.uuid,
jobToken: job.jobToken,
payload: { videoFile: 'video_short.mp4' } as any,
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an invalid vod audio merge payload', async function () {
const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
await server.videos.upload({ attributes, mode: 'legacy' })
await waitJobs([ server ])
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-audio-merge-transcoding' })
await server.runnerJobs.success({
jobUUID: job.uuid,
jobToken: job.jobToken,
payload: { hello: 'video_short.mp4' } as any,
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('Video studio', function () {
it('Should fail with an invalid video studio transcoding payload', async function () {
await server.runnerJobs.success({
jobUUID: studioAcceptedJob.uuid,
jobToken: studioAcceptedJob.jobToken,
payload: { hello: 'video_short.mp4' } as any,
runnerToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
})
describe('Job files', function () {
describe('Check video param for common job file routes', function () {
async function fetchFiles (options: {
videoUUID?: string
expectedStatus: HttpStatusCodeType
}) {
await fetchVideoInputFiles({ videoUUID, ...options, jobToken, jobUUID, runnerToken })
await fetchStudioFiles({
videoUUID: videoStudioUUID,
...options,
jobToken: studioAcceptedJob.jobToken,
jobUUID: studioAcceptedJob.uuid,
runnerToken,
studioFile
})
}
it('Should fail with an invalid video id', async function () {
await fetchFiles({
videoUUID: 'a',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown video id', async function () {
const videoUUID = '910ec12a-d9e6-458b-a274-0abb655f9464'
await fetchFiles({
videoUUID,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a video id not associated to this job', async function () {
await fetchFiles({
videoUUID: videoUUID2,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await fetchFiles({ expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('Video studio tasks file routes', function () {
it('Should fail with an invalid studio filename', async function () {
await fetchStudioFiles({
videoUUID: videoStudioUUID,
jobUUID: studioAcceptedJob.uuid,
runnerToken,
jobToken: studioAcceptedJob.jobToken,
studioFile: 'toto',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+278
ファイルの表示
@@ -0,0 +1,278 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
function updateSearchIndex (server: PeerTubeServer, enabled: boolean, disableLocalSearch = false) {
return server.config.updateExistingConfig({
newConfig: {
search: {
searchIndex: {
enabled,
disableLocalSearch
}
}
}
})
}
describe('Test videos API validator', function () {
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
})
describe('When searching videos', function () {
const path = '/api/v1/search/videos/'
const query = {
search: 'coucou'
}
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, null, query)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, null, query)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, null, query)
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail with an invalid category', async function () {
const customQuery1 = { ...query, categoryOneOf: [ 'aa', 'b' ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery2 = { ...query, categoryOneOf: 'a' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a valid category', async function () {
const customQuery1 = { ...query, categoryOneOf: [ 1, 7 ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
const customQuery2 = { ...query, categoryOneOf: 1 }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail with an invalid licence', async function () {
const customQuery1 = { ...query, licenceOneOf: [ 'aa', 'b' ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery2 = { ...query, licenceOneOf: 'a' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a valid licence', async function () {
const customQuery1 = { ...query, licenceOneOf: [ 1, 2 ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
const customQuery2 = { ...query, licenceOneOf: 1 }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should succeed with a valid language', async function () {
const customQuery1 = { ...query, languageOneOf: [ 'fr', 'en' ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
const customQuery2 = { ...query, languageOneOf: 'fr' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should succeed with valid tags', async function () {
const customQuery1 = { ...query, tagsOneOf: [ 'tag1', 'tag2' ] }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
const customQuery2 = { ...query, tagsOneOf: 'tag1' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
const customQuery3 = { ...query, tagsAllOf: [ 'tag1', 'tag2' ] }
await makeGetRequest({ url: server.url, path, query: customQuery3, expectedStatus: HttpStatusCode.OK_200 })
const customQuery4 = { ...query, tagsAllOf: 'tag1' }
await makeGetRequest({ url: server.url, path, query: customQuery4, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail with invalid durations', async function () {
const customQuery1 = { ...query, durationMin: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery2 = { ...query, durationMax: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with invalid dates', async function () {
const customQuery1 = { ...query, startDate: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery2 = { ...query, endDate: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery3 = { ...query, originallyPublishedStartDate: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery3, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const customQuery4 = { ...query, originallyPublishedEndDate: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery4, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an invalid host', async function () {
const customQuery = { ...query, host: '6565' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a host', async function () {
const customQuery = { ...query, host: 'example.com' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail with invalid uuids', async function () {
const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with valid uuids', async function () {
const customQuery = { ...query, uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When searching video playlists', function () {
const path = '/api/v1/search/video-playlists/'
const query = {
search: 'coucou',
host: 'example.com'
}
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, null, query)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, null, query)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, null, query)
})
it('Should fail with an invalid host', async function () {
await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with invalid uuids', async function () {
const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When searching video channels', function () {
const path = '/api/v1/search/video-channels/'
const query = {
search: 'coucou',
host: 'example.com'
}
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, null, query)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, null, query)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, null, query)
})
it('Should fail with an invalid host', async function () {
await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with invalid handles', async function () {
await makeGetRequest({ url: server.url, path, query: { ...query, handles: [ '' ] }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('Search target', function () {
it('Should fail/succeed depending on the search target', async function () {
const query = { search: 'coucou' }
const paths = [
'/api/v1/search/video-playlists/',
'/api/v1/search/video-channels/',
'/api/v1/search/videos/'
]
for (const path of paths) {
{
const customQuery = { ...query, searchTarget: 'hello' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
{
const customQuery = { ...query, searchTarget: undefined }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
}
{
const customQuery = { ...query, searchTarget: 'local' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
}
{
const customQuery = { ...query, searchTarget: 'search-index' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
await updateSearchIndex(server, true, true)
{
const customQuery = { ...query, searchTarget: 'search-index' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
}
await updateSearchIndex(server, true, false)
{
const customQuery = { ...query, searchTarget: 'local' }
await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
}
await updateSearchIndex(server, false, false)
}
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+207
ファイルの表示
@@ -0,0 +1,207 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
HttpStatusCode,
HttpStatusCodeType,
VideoCreateResult,
VideoPlaylistCreateResult,
VideoPlaylistPrivacy,
VideoPrivacy
} from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test services API validators', function () {
let server: PeerTubeServer
let playlistUUID: string
let privateVideo: VideoCreateResult
let unlistedVideo: VideoCreateResult
let privatePlaylist: VideoPlaylistCreateResult
let unlistedPlaylist: VideoPlaylistCreateResult
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
server.store.videoCreated = await server.videos.upload({ attributes: { name: 'my super name' } })
privateVideo = await server.videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE })
unlistedVideo = await server.videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED })
{
const created = await server.playlists.create({
attributes: {
displayName: 'super playlist',
privacy: VideoPlaylistPrivacy.PUBLIC,
videoChannelId: server.store.channel.id
}
})
playlistUUID = created.uuid
privatePlaylist = await server.playlists.create({
attributes: {
displayName: 'private',
privacy: VideoPlaylistPrivacy.PRIVATE,
videoChannelId: server.store.channel.id
}
})
unlistedPlaylist = await server.playlists.create({
attributes: {
displayName: 'unlisted',
privacy: VideoPlaylistPrivacy.UNLISTED,
videoChannelId: server.store.channel.id
}
})
}
})
describe('Test oEmbed API validators', function () {
it('Should fail with an invalid url', async function () {
const embedUrl = 'hello.com'
await checkParamEmbed(server, embedUrl)
})
it('Should fail with an invalid host', async function () {
const embedUrl = 'http://hello.com/videos/watch/' + server.store.videoCreated.uuid
await checkParamEmbed(server, embedUrl)
})
it('Should fail with an invalid element id', async function () {
const embedUrl = `${server.url}/videos/watch/blabla`
await checkParamEmbed(server, embedUrl)
})
it('Should fail with an unknown element', async function () {
const embedUrl = `${server.url}/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c`
await checkParamEmbed(server, embedUrl, HttpStatusCode.NOT_FOUND_404)
})
it('Should fail with an invalid path', async function () {
const embedUrl = `${server.url}/videos/watchs/${server.store.videoCreated.uuid}`
await checkParamEmbed(server, embedUrl)
})
it('Should fail with an invalid max height', async function () {
const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { maxheight: 'hello' })
})
it('Should fail with an invalid max width', async function () {
const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { maxwidth: 'hello' })
})
it('Should fail with an invalid format', async function () {
const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { format: 'blabla' })
})
it('Should fail with a non supported format', async function () {
const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.NOT_IMPLEMENTED_501, { format: 'xml' })
})
it('Should fail with a private video', async function () {
const embedUrl = `${server.url}/videos/watch/${privateVideo.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
})
it('Should fail with an unlisted video with the int id', async function () {
const embedUrl = `${server.url}/videos/watch/${unlistedVideo.id}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
})
it('Should succeed with an unlisted video using the uuid id', async function () {
for (const uuid of [ unlistedVideo.uuid, unlistedVideo.shortUUID ]) {
const embedUrl = `${server.url}/videos/watch/${uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200)
}
})
it('Should fail with a private playlist', async function () {
const embedUrl = `${server.url}/videos/watch/playlist/${privatePlaylist.uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
})
it('Should fail with an unlisted playlist using the int id', async function () {
const embedUrl = `${server.url}/videos/watch/playlist/${unlistedPlaylist.id}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
})
it('Should succeed with an unlisted playlist using the uuid id', async function () {
for (const uuid of [ unlistedPlaylist.uuid, unlistedPlaylist.shortUUID ]) {
const embedUrl = `${server.url}/videos/watch/playlist/${uuid}`
await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200)
}
})
it('Should succeed with the correct params with a video', async function () {
const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
const query = {
format: 'json',
maxheight: 400,
maxwidth: 400
}
await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200, query)
})
it('Should succeed with the correct params with a playlist', async function () {
const embedUrl = `${server.url}/videos/watch/playlist/${playlistUUID}`
const query = {
format: 'json',
maxheight: 400,
maxwidth: 400
}
await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200, query)
})
})
after(async function () {
await cleanupTests([ server ])
})
})
function checkParamEmbed (
server: PeerTubeServer,
embedUrl: string,
expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400,
query = {}
) {
const path = '/services/oembed'
return makeGetRequest({
url: server.url,
path,
query: Object.assign(query, { url: embedUrl }),
expectedStatus
})
}
+112
ファイルの表示
@@ -0,0 +1,112 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test transcoding API validators', function () {
let servers: PeerTubeServer[]
let userToken: string
let moderatorToken: string
let remoteId: string
let validId: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
{
const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
remoteId = uuid
}
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' })
validId = uuid
}
await waitJobs(servers)
await servers[0].config.enableTranscoding()
})
it('Should not run transcoding of a unknown video', async function () {
await servers[0].videos.runTranscoding({ videoId: 404, transcodingType: 'hls', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await servers[0].videos.runTranscoding({ videoId: 404, transcodingType: 'web-video', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not run transcoding of a remote video', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await servers[0].videos.runTranscoding({ videoId: remoteId, transcodingType: 'hls', expectedStatus })
await servers[0].videos.runTranscoding({ videoId: remoteId, transcodingType: 'web-video', expectedStatus })
})
it('Should not run transcoding by a non admin user', async function () {
const expectedStatus = HttpStatusCode.FORBIDDEN_403
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls', token: userToken, expectedStatus })
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', token: moderatorToken, expectedStatus })
})
it('Should not run transcoding without transcoding type', async function () {
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should not run transcoding with an incorrect transcoding type', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'toto' as any, expectedStatus })
})
it('Should not run transcoding if the instance disabled it', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await servers[0].config.disableTranscoding()
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls', expectedStatus })
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', expectedStatus })
})
it('Should run transcoding', async function () {
this.timeout(120_000)
await servers[0].config.enableTranscoding()
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls' })
await waitJobs(servers)
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', forceTranscoding: true })
await waitJobs(servers)
})
it('Should not run transcoding on a video that is already being transcoded if forceTranscoding is not set', async function () {
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video' })
const expectedStatus = HttpStatusCode.CONFLICT_409
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', expectedStatus })
await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', forceTranscoding: true })
})
after(async function () {
await cleanupTests(servers)
})
})
+294
ファイルの表示
@@ -0,0 +1,294 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
TwoFactorCommand
} from '@peertube/peertube-server-commands'
describe('Test two factor API validators', function () {
let server: PeerTubeServer
let rootId: number
let rootPassword: string
let rootRequestToken: string
let rootOTPToken: string
let userId: number
let userToken = ''
let userPassword: string
let userRequestToken: string
let userOTPToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
{
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
}
{
const result = await server.users.generate('user1')
userToken = result.token
userId = result.userId
userPassword = result.password
}
{
const { id } = await server.users.getMyInfo()
rootId = id
rootPassword = server.store.user.password
}
})
describe('When requesting two factor', function () {
it('Should fail with an unknown user id', async function () {
await server.twoFactor.request({ userId: 42, currentPassword: rootPassword, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an invalid user id', async function () {
await server.twoFactor.request({
userId: 'invalid' as any,
currentPassword: rootPassword,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to request another user two factor without the appropriate rights', async function () {
await server.twoFactor.request({
userId: rootId,
token: userToken,
currentPassword: userPassword,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to request another user two factor with the appropriate rights', async function () {
await server.twoFactor.request({ userId, currentPassword: rootPassword })
})
it('Should fail to request two factor without a password', async function () {
await server.twoFactor.request({
userId,
token: userToken,
currentPassword: undefined,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to request two factor with an incorrect password', async function () {
await server.twoFactor.request({
userId,
token: userToken,
currentPassword: rootPassword,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to request two factor without a password when targeting a remote user with an admin account', async function () {
await server.twoFactor.request({ userId })
})
it('Should fail to request two factor without a password when targeting myself with an admin account', async function () {
await server.twoFactor.request({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.twoFactor.request({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed to request my two factor auth', async function () {
{
const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword })
userRequestToken = otpRequest.requestToken
userOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
}
{
const { otpRequest } = await server.twoFactor.request({ userId: rootId, currentPassword: rootPassword })
rootRequestToken = otpRequest.requestToken
rootOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
}
})
})
describe('When confirming two factor request', function () {
it('Should fail with an unknown user id', async function () {
await server.twoFactor.confirmRequest({
userId: 42,
requestToken: rootRequestToken,
otpToken: rootOTPToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an invalid user id', async function () {
await server.twoFactor.confirmRequest({
userId: 'invalid' as any,
requestToken: rootRequestToken,
otpToken: rootOTPToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to confirm another user two factor request without the appropriate rights', async function () {
await server.twoFactor.confirmRequest({
userId: rootId,
token: userToken,
requestToken: rootRequestToken,
otpToken: rootOTPToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail without request token', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: undefined,
otpToken: userOTPToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an invalid request token', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: 'toto',
otpToken: userOTPToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with request token of another user', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: rootRequestToken,
otpToken: userOTPToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail without an otp token', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: userRequestToken,
otpToken: undefined,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad otp token', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: userRequestToken,
otpToken: '123456',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to confirm another user two factor request with the appropriate rights', async function () {
await server.twoFactor.confirmRequest({
userId,
requestToken: userRequestToken,
otpToken: userOTPToken
})
// Reinit
await server.twoFactor.disable({ userId, currentPassword: rootPassword })
})
it('Should succeed to confirm my two factor request', async function () {
await server.twoFactor.confirmRequest({
userId,
token: userToken,
requestToken: userRequestToken,
otpToken: userOTPToken
})
})
it('Should fail to confirm again two factor request', async function () {
await server.twoFactor.confirmRequest({
userId,
token: userToken,
requestToken: userRequestToken,
otpToken: userOTPToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
describe('When disabling two factor', function () {
it('Should fail with an unknown user id', async function () {
await server.twoFactor.disable({
userId: 42,
currentPassword: rootPassword,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an invalid user id', async function () {
await server.twoFactor.disable({
userId: 'invalid' as any,
currentPassword: rootPassword,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to disable another user two factor without the appropriate rights', async function () {
await server.twoFactor.disable({
userId: rootId,
token: userToken,
currentPassword: userPassword,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail to disable two factor with an incorrect password', async function () {
await server.twoFactor.disable({
userId,
token: userToken,
currentPassword: rootPassword,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to disable two factor without a password when targeting a remote user with an admin account', async function () {
await server.twoFactor.disable({ userId })
await server.twoFactor.requestAndConfirm({ userId })
})
it('Should fail to disable two factor without a password when targeting myself with an admin account', async function () {
await server.twoFactor.disable({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.twoFactor.disable({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed to disable another user two factor with the appropriate rights', async function () {
await server.twoFactor.disable({ userId, currentPassword: rootPassword })
await server.twoFactor.requestAndConfirm({ userId })
})
it('Should succeed to update my two factor auth', async function () {
await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword })
})
it('Should fail to disable again two factor', async function () {
await server.twoFactor.disable({
userId,
token: userToken,
currentPassword: userPassword,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+134
ファイルの表示
@@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { randomInt } from '@peertube/peertube-core-utils'
import { HttpStatusCode, VideoImportState, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
VideosCommand,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test upload quota', function () {
let server: PeerTubeServer
let rootId: number
let command: VideosCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
const user = await server.users.getMyInfo()
rootId = user.id
await server.users.update({ userId: rootId, videoQuota: 42 })
command = server.videos
})
describe('When having a video quota', function () {
it('Should fail with a registered user having too many videos with legacy upload', async function () {
this.timeout(120000)
const user = { username: 'registered' + randomInt(1, 1500), password: 'password' }
await server.registrations.register(user)
const userToken = await server.login.getAccessToken(user)
const attributes = { fixture: 'video_short2.webm' }
for (let i = 0; i < 5; i++) {
await command.upload({ token: userToken, attributes })
}
await command.upload({ token: userToken, attributes, expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
})
it('Should fail with a registered user having too many videos with resumable upload', async function () {
this.timeout(120000)
const user = { username: 'registered' + randomInt(1, 1500), password: 'password' }
await server.registrations.register(user)
const userToken = await server.login.getAccessToken(user)
const attributes = { fixture: 'video_short2.webm' }
for (let i = 0; i < 5; i++) {
await command.upload({ token: userToken, attributes })
}
await command.upload({ token: userToken, attributes, expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
})
it('Should fail to import with HTTP/Torrent/magnet', async function () {
this.timeout(120_000)
const baseAttributes = {
channelId: server.store.channel.id,
privacy: VideoPrivacy.PUBLIC
}
await server.videoImports.importVideo({ attributes: { ...baseAttributes, targetUrl: FIXTURE_URLS.goodVideo } })
await server.videoImports.importVideo({ attributes: { ...baseAttributes, magnetUri: FIXTURE_URLS.magnet } })
await server.videoImports.importVideo({ attributes: { ...baseAttributes, torrentfile: 'video-720p.torrent' as any } })
await waitJobs([ server ])
const { total, data: videoImports } = await server.videoImports.getMyVideoImports()
expect(total).to.equal(3)
expect(videoImports).to.have.lengthOf(3)
for (const videoImport of videoImports) {
expect(videoImport.state.id).to.equal(VideoImportState.FAILED)
expect(videoImport.error).not.to.be.undefined
expect(videoImport.error).to.contain('user video quota is exceeded')
}
})
})
describe('When having a daily video quota', function () {
it('Should fail with a user having too many videos daily', async function () {
await server.users.update({ userId: rootId, videoQuotaDaily: 42 })
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
})
})
describe('When having an absolute and daily video quota', function () {
it('Should fail if exceeding total quota', async function () {
await server.users.update({
userId: rootId,
videoQuota: 42,
videoQuotaDaily: 1024 * 1024 * 1024
})
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
})
it('Should fail if exceeding daily quota', async function () {
await server.users.update({
userId: rootId,
videoQuota: 1024 * 1024 * 1024,
videoQuotaDaily: 42
})
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+339
ファイルの表示
@@ -0,0 +1,339 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test user export API validators', function () {
let server: PeerTubeServer
let rootId: number
let userId: number
let userToken: string
let exportId: number
let userExportId: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
{
const user = await server.users.getMyInfo()
rootId = user.id
}
{
userToken = await server.users.generateUserAndToken('user')
const user = await server.users.getMyInfo({ token: userToken })
userId = user.id
}
})
describe('Request export', function () {
it('Should fail if export is disabled', async function () {
await server.config.disableUserExport()
await server.userExports.request({ userId: rootId, withVideoFiles: false, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.config.enableUserExport()
})
it('Should fail without token', async function () {
await server.userExports.request({
userId: rootId,
withVideoFiles: false,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with invalid token', async function () {
await server.userExports.request({
userId: rootId,
withVideoFiles: false,
token: 'hello',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a token of another user', async function () {
await server.userExports.request({
userId: rootId,
withVideoFiles: false,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown user', async function () {
await server.userExports.request({ userId: 404, withVideoFiles: false, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail if user quota is too big', async function () {
const { videoQuotaUsed } = await server.users.getMyQuotaUsed()
await server.config.updateExistingConfig({
newConfig: {
export: {
users: { maxUserVideoQuota: videoQuotaUsed - 1 }
}
}
})
await server.userExports.request({ userId: rootId, withVideoFiles: true, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await server.userExports.request({ userId: rootId, withVideoFiles: false, expectedStatus: HttpStatusCode.OK_200 })
// Cleanup
await server.userExports.waitForCreation({ userId: rootId })
await server.userExports.deleteAllArchives({ userId: rootId })
await server.config.updateExistingConfig({
newConfig: {
export: {
users: { maxUserVideoQuota: 1000 * 1000 * 1000 * 1000 }
}
}
})
})
it('Should succeed with the appropriate token', async function () {
const { export: { id } } = await server.userExports.request({ userId: rootId, withVideoFiles: false })
exportId = id
})
it('Should fail if there is already an export', async function () {
await server.userExports.request({
userId: rootId,
withVideoFiles: false,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed after a delete with an admin token', async function () {
await server.userExports.waitForCreation({ userId: rootId })
await server.userExports.delete({ userId: rootId, exportId })
const { export: { id } } = await server.userExports.request({ userId: rootId, withVideoFiles: false })
exportId = id
})
})
describe('List exports', function () {
it('Should fail if export is disabled', async function () {
await server.config.disableUserExport()
await server.userExports.list({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.config.enableUserExport()
})
it('Should fail without token', async function () {
await server.userExports.list({
userId: rootId,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with invalid token', async function () {
await server.userExports.list({
userId: rootId,
token: 'toto',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a token of another user', async function () {
await server.userExports.list({
userId: rootId,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown user', async function () {
await server.userExports.list({ userId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct parameters', async function () {
// User token
await server.userExports.list({ userId, token: userToken })
// Root token
await server.userExports.list({ userId })
})
})
describe('Deleting export', function () {
before(async function () {
const { export: { id } } = await server.userExports.request({ userId, withVideoFiles: true })
userExportId = id
await server.userExports.waitForCreation({ userId })
})
it('Should fail if export is disabled', async function () {
await server.config.disableUserExport()
await server.userExports.delete({ userId, exportId: userExportId, token: userToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.config.enableUserExport()
})
it('Should fail without token', async function () {
await server.userExports.delete({
userId: rootId,
exportId,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with invalid token', async function () {
await server.userExports.delete({
userId: rootId,
exportId,
token: 'toto',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a token of another user', async function () {
await server.userExports.delete({
userId: rootId,
exportId,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an export id of another user', async function () {
await server.userExports.delete({
userId,
exportId,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown user', async function () {
await server.userExports.delete({
userId: 404,
exportId,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an unknown export id', async function () {
await server.userExports.delete({
userId,
exportId: 404,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
await server.userExports.delete({
userId,
exportId: userExportId,
token: userToken
})
})
})
describe('Downloading an export', function () {
before(async function () {
await server.userExports.request({ userId, withVideoFiles: true })
await server.userExports.waitForCreation({ userId })
})
it('Should fail without jwt token', async function () {
const { data } = await server.userExports.list({ userId })
const url = data[0].privateDownloadUrl.replace('jwt=', 'toto=')
await makeRawRequest({ url, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a wrong jwt token', async function () {
const { data } = await server.userExports.list({ userId })
// Invalid format
{
const url = data[0].privateDownloadUrl.replace('jwt=', 'jwt=hello.coucou')
await makeRawRequest({ url, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
// Invalid content
{
const url = data[0].privateDownloadUrl.replace('jwt=', 'jwt=a')
await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should fail with a jwt token of another export', async function () {
let userQuery: string
// Save user JWT token
{
const { data } = await server.userExports.list({ userId })
const { pathname, search } = new URL(data[0].privateDownloadUrl)
const rawQuery = search.replace('?', '')
userQuery = rawQuery
await makeGetRequest({ url: server.url, path: pathname, rawQuery, expectedStatus: HttpStatusCode.OK_200 })
}
// This user JWT token must not be used to download an export of another user
{
const { data } = await server.userExports.list({ userId: rootId })
const { pathname, search } = new URL(data[0].privateDownloadUrl)
const rawQuery = search.replace('?', '')
await makeGetRequest({ url: server.url, path: pathname, rawQuery, expectedStatus: HttpStatusCode.OK_200 })
await makeGetRequest({ url: server.url, path: pathname, rawQuery: userQuery, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should fail with an invalid filename', async function () {
const { data } = await server.userExports.list({ userId })
const url = data[0].privateDownloadUrl.replace('.zip', '.tar')
await makeRawRequest({ url, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an expired JWT token', async function () {
const { data } = await server.userExports.list({ userId })
await wait(3000)
await makeRawRequest({ url: data[0].privateDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
const { data } = await server.userExports.list({ userId })
await makeRawRequest({ url: data[0].privateDownloadUrl, expectedStatus: HttpStatusCode.OK_200 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+169
ファイルの表示
@@ -0,0 +1,169 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer, PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test user import API validators', function () {
let server: PeerTubeServer
let userId: number
let rootId: number
let token: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
{
const result = await server.users.generate('user')
userId = result.userId
token = result.token
}
{
const { id } = await server.users.getMyInfo()
rootId = id
}
})
describe('Request import', function () {
it('Should fail if import is disabled', async function () {
await server.config.disableUserImport()
await server.userImports.importArchive({
userId,
fixture: 'export-without-files.zip',
token,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await server.config.enableUserImport()
})
it('Should fail without token', async function () {
await server.userImports.importArchive({
userId,
fixture: 'export-without-files.zip',
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with invalid token', async function () {
await server.userImports.importArchive({
userId,
fixture: 'export-without-files.zip',
token: 'invalid',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a token of another user', async function () {
await server.userImports.importArchive({
userId: rootId,
fixture: 'export-without-files.zip',
token,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an unknown user', async function () {
await server.userImports.importArchive({
userId: 404,
fixture: 'export-without-files.zip',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail if user quota is exceeded', async function () {
await server.users.update({ userId, videoQuota: 100 })
await server.userImports.importArchive({
userId,
fixture: 'export-without-files.zip',
expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
})
await server.users.update({ userId, videoQuota: -1 })
})
it('Should succeed with the correct params', async function () {
await server.userImports.importArchive({ userId, fixture: 'export-without-files.zip' })
await waitJobs([ server ])
})
it('Should fail with an import that is already being processed', async function () {
await server.userImports.importArchive({ userId, fixture: 'export-without-files.zip' })
await server.userImports.importArchive({
userId,
fixture: 'export-without-files.zip',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with invalid ZIPs', async function () {
this.timeout(120000)
const toTest = [
'export-bad-video-file.zip',
'export-bad-video.zip',
'export-without-videos.zip',
'export-bad-structure.zip',
'export-bad-structure.zip'
]
const tokens: string[] = []
for (let i = 0; i < toTest.length; i++) {
const { token, userId } = await server.users.generate('import' + i)
await server.userImports.importArchive({ userId, token, fixture: toTest[i] })
}
await waitJobs([ server ])
for (const token of tokens) {
const { data } = await server.videos.listMyVideos({ token })
expect(data).to.have.lengthOf(0)
}
})
})
describe('Get latest import status', function () {
it('Should fail without token', async function () {
await server.userImports.getLatestImport({ userId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with invalid token', async function () {
await server.userImports.getLatestImport({ userId, token: 'invalid', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an unknown user', async function () {
await server.userImports.getLatestImport({ userId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a token of another user', async function () {
await server.userImports.getLatestImport({ userId: rootId, token, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct parameters', async function () {
await server.userImports.getLatestImport({ userId, token })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+291
ファイルの表示
@@ -0,0 +1,291 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { io } from 'socket.io-client'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { wait } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserNotificationSetting, UserNotificationSettingValue } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test user notifications API validators', function () {
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
})
describe('When listing my notifications', function () {
const path = '/api/v1/users/me/notifications'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect unread parameter', async function () {
await makeGetRequest({
url: server.url,
path,
query: {
unread: 'toto'
},
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When marking as read my notifications', function () {
const path = '/api/v1/users/me/notifications/read'
it('Should fail with wrong ids parameters', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: {
ids: [ 'hello' ]
},
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makePostBodyRequest({
url: server.url,
path,
fields: {
ids: [ ]
},
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makePostBodyRequest({
url: server.url,
path,
fields: {
ids: 5
},
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a non authenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: {
ids: [ 5 ]
},
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: {
ids: [ 5 ]
},
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When marking as read my notifications', function () {
const path = '/api/v1/users/me/notifications/read-all'
it('Should fail with a non authenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When updating my notification settings', function () {
const path = '/api/v1/users/me/notification-settings'
const correctFields: UserNotificationSetting = {
newVideoFromSubscription: UserNotificationSettingValue.WEB,
newCommentOnMyVideo: UserNotificationSettingValue.WEB,
abuseAsModerator: UserNotificationSettingValue.WEB,
videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB,
blacklistOnMyVideo: UserNotificationSettingValue.WEB,
myVideoImportFinished: UserNotificationSettingValue.WEB,
myVideoPublished: UserNotificationSettingValue.WEB,
commentMention: UserNotificationSettingValue.WEB,
newFollow: UserNotificationSettingValue.WEB,
newUserRegistration: UserNotificationSettingValue.WEB,
newInstanceFollower: UserNotificationSettingValue.WEB,
autoInstanceFollowing: UserNotificationSettingValue.WEB,
abuseNewMessage: UserNotificationSettingValue.WEB,
abuseStateChange: UserNotificationSettingValue.WEB,
newPeerTubeVersion: UserNotificationSettingValue.WEB,
myVideoStudioEditionFinished: UserNotificationSettingValue.WEB,
myVideoTranscriptionGenerated: UserNotificationSettingValue.WEB,
newPluginVersion: UserNotificationSettingValue.WEB
}
it('Should fail with missing fields', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { newVideoFromSubscription: UserNotificationSettingValue.WEB },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with incorrect field values', async function () {
{
const fields = { ...correctFields, newCommentOnMyVideo: 15 }
await makePutBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
{
const fields = { ...correctFields, newCommentOnMyVideo: 'toto' }
await makePutBodyRequest({
url: server.url,
path,
fields,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
})
it('Should fail with a non authenticated user', async function () {
await makePutBodyRequest({
url: server.url,
path,
fields: correctFields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: correctFields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When connecting to my notification socket', function () {
it('Should fail with no token', function (next) {
const socket = io(`${server.url}/user-notifications`, { reconnection: false })
socket.once('connect_error', function () {
socket.disconnect()
next()
})
socket.on('connect', () => {
socket.disconnect()
next(new Error('Connected with a missing token.'))
})
})
it('Should fail with an invalid token', function (next) {
const socket = io(`${server.url}/user-notifications`, {
query: { accessToken: 'bad_access_token' },
reconnection: false
})
socket.once('connect_error', function () {
socket.disconnect()
next()
})
socket.on('connect', () => {
socket.disconnect()
next(new Error('Connected with an invalid token.'))
})
})
it('Should success with the correct token', function (next) {
const socket = io(`${server.url}/user-notifications`, {
query: { accessToken: server.accessToken },
reconnection: false
})
function errorListener (err) {
next(new Error('Error in connection: ' + err))
}
socket.on('connect_error', errorListener)
socket.once('connect', async () => {
socket.disconnect()
await wait(500)
next()
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+298
ファイルの表示
@@ -0,0 +1,298 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { HttpStatusCode } from '@peertube/peertube-models'
import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@tests/shared/checks.js'
describe('Test user subscriptions API validators', function () {
const path = '/api/v1/users/me/subscriptions'
let server: PeerTubeServer
let userAccessToken = ''
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const user = {
username: 'user1',
password: 'my super password'
}
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
})
describe('When listing my subscriptions', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When listing my subscriptions videos', function () {
const path = '/api/v1/users/me/subscriptions/videos'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When adding a subscription', function () {
it('Should fail with a non authenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { uri: 'user1_channel@' + server.host },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with bad URIs', async function () {
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { uri: 'root' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { uri: 'root@' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { uri: 'root@hello@' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
this.timeout(20000)
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: { uri: 'user1_channel@' + server.host },
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
await waitJobs([ server ])
})
})
describe('When getting a subscription', function () {
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path: path + '/user1_channel@' + server.host,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with bad URIs', async function () {
await makeGetRequest({
url: server.url,
path: path + '/root',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeGetRequest({
url: server.url,
path: path + '/root@',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeGetRequest({
url: server.url,
path: path + '/root@hello@',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown subscription', async function () {
await makeGetRequest({
url: server.url,
path: path + '/root1@' + server.host,
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path: path + '/user1_channel@' + server.host,
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When checking if subscriptions exist', function () {
const existPath = path + '/exist'
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path: existPath,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with bad URIs', async function () {
await makeGetRequest({
url: server.url,
path: existPath,
query: { uris: 'toto' },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeGetRequest({
url: server.url,
path: existPath,
query: { 'uris[]': 1 },
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path: existPath,
query: { 'uris[]': 'coucou@' + server.host },
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When removing a subscription', function () {
it('Should fail with a non authenticated user', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1_channel@' + server.host,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with bad URIs', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/root',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeDeleteRequest({
url: server.url,
path: path + '/root@',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeDeleteRequest({
url: server.url,
path: path + '/root@hello@',
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown subscription', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/root1@' + server.host,
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '/user1_channel@' + server.host,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+457
ファイルの表示
@@ -0,0 +1,457 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserAdminFlag, UserRole } from '@peertube/peertube-models'
import {
cleanupTests,
ConfigCommand,
createSingleServer,
killallServers,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test users admin API validators', function () {
const path = '/api/v1/users/'
let userId: number
let rootId: number
let moderatorId: number
let server: PeerTubeServer
let userToken = ''
let moderatorToken = ''
let emailPort: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
const emails: object[] = []
emailPort = await MockSmtpServer.Instance.collectEmails(emails)
{
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
}
{
const result = await server.users.generate('user1')
userToken = result.token
userId = result.userId
}
{
const result = await server.users.generate('moderator1', UserRole.MODERATOR)
moderatorToken = result.token
}
{
const result = await server.users.generate('moderator2', UserRole.MODERATOR)
moderatorId = result.userId
}
})
describe('When listing users', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({
url: server.url,
path,
token: userToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
describe('When adding a new user', function () {
const baseCorrectParams = {
username: 'user2',
email: 'test@example.com',
password: 'my super password',
videoQuota: -1,
videoQuotaDaily: -1,
role: UserRole.USER,
adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST
}
it('Should fail with a too small username', async function () {
const fields = { ...baseCorrectParams, username: '' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a too long username', async function () {
const fields = { ...baseCorrectParams, username: 'super'.repeat(50) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a not lowercase username', async function () {
const fields = { ...baseCorrectParams, username: 'Toto' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an incorrect username', async function () {
const fields = { ...baseCorrectParams, username: 'my username' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a missing email', async function () {
const fields = omit(baseCorrectParams, [ 'email' ])
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an invalid email', async function () {
const fields = { ...baseCorrectParams, email: 'test_example.com' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a too small password', async function () {
const fields = { ...baseCorrectParams, password: 'bla' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a too long password', async function () {
const fields = { ...baseCorrectParams, password: 'super'.repeat(61) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with empty password and no smtp configured', async function () {
const fields = { ...baseCorrectParams, password: '' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should succeed with no password on a server with smtp enabled', async function () {
this.timeout(20000)
await killallServers([ server ])
await server.run(ConfigCommand.getEmailOverrideConfig(emailPort))
const fields = {
...baseCorrectParams,
password: '',
username: 'create_password',
email: 'create_password@example.com'
}
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail with invalid admin flags', async function () {
const fields = { ...baseCorrectParams, adminFlags: 'toto' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an non authenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path,
token: 'supertoken',
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail if we add a user with the same username', async function () {
const fields = { ...baseCorrectParams, username: 'user1' }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail if we add a user with the same email', async function () {
const fields = { ...baseCorrectParams, email: 'user1@example.com' }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail with an invalid videoQuota', async function () {
const fields = { ...baseCorrectParams, videoQuota: -5 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an invalid videoQuotaDaily', async function () {
const fields = { ...baseCorrectParams, videoQuotaDaily: -7 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail without a user role', async function () {
const fields = omit(baseCorrectParams, [ 'role' ])
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an invalid user role', async function () {
const fields = { ...baseCorrectParams, role: 88989 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a "peertube" username', async function () {
const fields = { ...baseCorrectParams, username: 'peertube' }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should fail to create a moderator or an admin with a moderator', async function () {
for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) {
const fields = { ...baseCorrectParams, role }
await makePostBodyRequest({
url: server.url,
path,
token: moderatorToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should succeed to create a user with a moderator', async function () {
const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER }
await makePostBodyRequest({
url: server.url,
path,
token: moderatorToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail with a non admin user', async function () {
const user = { username: 'user1' }
userToken = await server.login.getAccessToken(user)
const fields = {
username: 'user3',
email: 'test@example.com',
password: 'my super password',
videoQuota: 42000000
}
await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
})
describe('When getting a user', function () {
it('Should fail with an non authenticated user', async function () {
await makeGetRequest({
url: server.url,
path: path + userId,
token: 'supertoken',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When updating a user', function () {
it('Should fail with an invalid email attribute', async function () {
const fields = {
email: 'blabla'
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with an invalid emailVerified attribute', async function () {
const fields = {
emailVerified: 'yes'
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with an invalid videoQuota attribute', async function () {
const fields = {
videoQuota: -90
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with an invalid user role attribute', async function () {
const fields = {
role: 54878
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with a too small password', async function () {
const fields = {
currentPassword: 'password',
password: 'bla'
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with a too long password', async function () {
const fields = {
currentPassword: 'password',
password: 'super'.repeat(61)
}
await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
})
it('Should fail with an non authenticated user', async function () {
const fields = {
videoQuota: 42
}
await makePutBodyRequest({
url: server.url,
path: path + userId,
token: 'supertoken',
fields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail when updating root role', async function () {
const fields = {
role: UserRole.MODERATOR
}
await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields })
})
it('Should fail with invalid admin flags', async function () {
const fields = { adminFlags: 'toto' }
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail to update an admin with a moderator', async function () {
const fields = {
videoQuota: 42
}
await makePutBodyRequest({
url: server.url,
path: path + moderatorId,
token: moderatorToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to update a user with a moderator', async function () {
const fields = {
videoQuota: 42
}
await makePutBodyRequest({
url: server.url,
path: path + userId,
token: moderatorToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should succeed with the correct params', async function () {
const fields = {
email: 'email@example.com',
emailVerified: true,
videoQuota: 42,
role: UserRole.USER
}
await makePutBodyRequest({
url: server.url,
path: path + userId,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests([ server ])
})
})
+122
ファイルの表示
@@ -0,0 +1,122 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test users API validators', function () {
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1, {
rates_limit: {
ask_send_email: {
max: 10
}
}
})
await setAccessTokensToServers([ server ])
await server.config.enableSignup(true)
await server.users.generate('moderator2', UserRole.MODERATOR)
await server.registrations.requestRegistration({
username: 'request1',
registrationReason: 'tt'
})
})
describe('When asking a password reset', function () {
const path = '/api/v1/users/ask-reset-password'
it('Should fail with a missing email', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should fail with an invalid email', async function () {
const fields = { email: 'hello' }
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should success with the correct params', async function () {
const fields = { email: 'admin@example.com' }
await makePostBodyRequest({
url: server.url,
path,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When asking for an account verification email', function () {
const path = '/api/v1/users/ask-send-verify-email'
it('Should fail with a missing email', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should fail with an invalid email', async function () {
const fields = { email: 'hello' }
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should succeed with the correct params', async function () {
const fields = { email: 'admin@example.com' }
await makePostBodyRequest({
url: server.url,
path,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When asking for a registration verification email', function () {
const path = '/api/v1/users/registrations/ask-send-verify-email'
it('Should fail with a missing email', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should fail with an invalid email', async function () {
const fields = { email: 'hello' }
await makePostBodyRequest({ url: server.url, path, fields })
})
it('Should succeed with the correct params', async function () {
const fields = { email: 'request1@example.com' }
await makePostBodyRequest({
url: server.url,
path,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+292
ファイルの表示
@@ -0,0 +1,292 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode, VideoBlacklistType } from '@peertube/peertube-models'
import {
BlacklistCommand,
cleanupTests,
createMultipleServers,
doubleFollow,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video blacklist API validators', function () {
let servers: PeerTubeServer[]
let notBlacklistedVideoId: string
let remoteVideoUUID: string
let userAccessToken1 = ''
let userAccessToken2 = ''
let command: BlacklistCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
{
const username = 'user1'
const password = 'my super password'
await servers[0].users.create({ username, password })
userAccessToken1 = await servers[0].login.getAccessToken({ username, password })
}
{
const username = 'user2'
const password = 'my super password'
await servers[0].users.create({ username, password })
userAccessToken2 = await servers[0].login.getAccessToken({ username, password })
}
{
servers[0].store.videoCreated = await servers[0].videos.upload({ token: userAccessToken1 })
}
{
const { uuid } = await servers[0].videos.upload()
notBlacklistedVideoId = uuid
}
{
const { uuid } = await servers[1].videos.upload()
remoteVideoUUID = uuid
}
await waitJobs(servers)
command = servers[0].blacklist
})
describe('When adding a video in blacklist', function () {
const basePath = '/api/v1/videos/'
it('Should fail with nothing', async function () {
const path = basePath + servers[0].store.videoCreated + '/blacklist'
const fields = {}
await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
})
it('Should fail with a wrong video', async function () {
const wrongPath = '/api/v1/videos/blabla/blacklist'
const fields = {}
await makePostBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
})
it('Should fail with a non authenticated user', async function () {
const path = basePath + servers[0].store.videoCreated + '/blacklist'
const fields = {}
await makePostBodyRequest({ url: servers[0].url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a non admin user', async function () {
const path = basePath + servers[0].store.videoCreated + '/blacklist'
const fields = {}
await makePostBodyRequest({
url: servers[0].url,
path,
token: userAccessToken2,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid reason', async function () {
const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
const fields = { reason: 'a'.repeat(305) }
await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
})
it('Should fail to unfederate a remote video', async function () {
const path = basePath + remoteVideoUUID + '/blacklist'
const fields = { unfederate: true }
await makePostBodyRequest({
url: servers[0].url,
path,
token: servers[0].accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
const fields = {}
await makePostBodyRequest({
url: servers[0].url,
path,
token: servers[0].accessToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When updating a video in blacklist', function () {
const basePath = '/api/v1/videos/'
it('Should fail with a wrong video', async function () {
const wrongPath = '/api/v1/videos/blabla/blacklist'
const fields = {}
await makePutBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
})
it('Should fail with a video not blacklisted', async function () {
const path = '/api/v1/videos/' + notBlacklistedVideoId + '/blacklist'
const fields = {}
await makePutBodyRequest({
url: servers[0].url,
path,
token: servers[0].accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a non authenticated user', async function () {
const path = basePath + servers[0].store.videoCreated + '/blacklist'
const fields = {}
await makePutBodyRequest({ url: servers[0].url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a non admin user', async function () {
const path = basePath + servers[0].store.videoCreated + '/blacklist'
const fields = {}
await makePutBodyRequest({
url: servers[0].url,
path,
token: userAccessToken2,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid reason', async function () {
const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
const fields = { reason: 'a'.repeat(305) }
await makePutBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
})
it('Should succeed with the correct params', async function () {
const path = basePath + servers[0].store.videoCreated.shortUUID + '/blacklist'
const fields = { reason: 'hello' }
await makePutBodyRequest({
url: servers[0].url,
path,
token: servers[0].accessToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When getting blacklisted video', function () {
it('Should fail with a non authenticated user', async function () {
await servers[0].videos.get({ id: servers[0].store.videoCreated.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user', async function () {
await servers[0].videos.getWithToken({
token: userAccessToken2,
id: servers[0].store.videoCreated.uuid,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the owner authenticated user', async function () {
const video = await servers[0].videos.getWithToken({ token: userAccessToken1, id: servers[0].store.videoCreated.uuid })
expect(video.blacklisted).to.be.true
})
it('Should succeed with an admin', async function () {
const video = servers[0].store.videoCreated
for (const id of [ video.id, video.uuid, video.shortUUID ]) {
const video = await servers[0].videos.getWithToken({ id, expectedStatus: HttpStatusCode.OK_200 })
expect(video.blacklisted).to.be.true
}
})
})
describe('When removing a video in blacklist', function () {
it('Should fail with a non authenticated user', async function () {
await command.remove({
token: 'faketoken',
videoId: servers[0].store.videoCreated.uuid,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a non admin user', async function () {
await command.remove({
token: userAccessToken2,
videoId: servers[0].store.videoCreated.uuid,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an incorrect id', async function () {
await command.remove({ videoId: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a not blacklisted video', async function () {
// The video was not added to the blacklist so it should fail
await command.remove({ videoId: notBlacklistedVideoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await command.remove({ videoId: servers[0].store.videoCreated.uuid, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
})
})
describe('When listing videos in blacklist', function () {
const basePath = '/api/v1/videos/blacklist/'
it('Should fail with a non authenticated user', async function () {
await servers[0].blacklist.list({ token: 'faketoken', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a non admin user', async function () {
await servers[0].blacklist.list({ token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(servers[0].url, basePath, servers[0].accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(servers[0].url, basePath, servers[0].accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken)
})
it('Should fail with an invalid type', async function () {
await servers[0].blacklist.list({ type: 0 as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct parameters', async function () {
await servers[0].blacklist.list({ type: VideoBlacklistType.MANUAL })
})
})
after(async function () {
await cleanupTests(servers)
})
})
+312
ファイルの表示
@@ -0,0 +1,312 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makeUploadRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test video captions API validator', function () {
const path = '/api/v1/videos/'
let server: PeerTubeServer
let userAccessToken: string
let video: VideoCreateResult
let privateVideo: VideoCreateResult
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
video = await server.videos.upload()
privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
userAccessToken = await server.users.generateUserAndToken('user1')
})
describe('When adding video caption', function () {
const fields = { }
const attaches = {
captionfile: buildAbsoluteFixturePath('subtitle-good1.vtt')
}
it('Should fail without a valid uuid', async function () {
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions/fr',
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with an unknown id', async function () {
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/fr',
token: server.accessToken,
fields,
attaches,
expectedStatus: 404
})
})
it('Should fail with a missing language in path', async function () {
const captionPath = path + video.uuid + '/captions'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with an unknown language', async function () {
const captionPath = path + video.uuid + '/captions/15'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail without access token', async function () {
const captionPath = path + video.uuid + '/captions/fr'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
fields,
attaches,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad access token', async function () {
const captionPath = path + video.uuid + '/captions/fr'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
token: 'blabla',
fields,
attaches,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
const captionPath = path + video.uuid + '/captions/fr'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
token: userAccessToken,
fields,
attaches,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
// We accept any file now
// it('Should fail with an invalid captionfile extension', async function () {
// const attaches = {
// 'captionfile': buildAbsoluteFixturePath('subtitle-bad.txt')
// }
//
// const captionPath = path + video.uuid + '/captions/fr'
// await makeUploadRequest({
// method: 'PUT',
// url: server.url,
// path: captionPath,
// token: server.accessToken,
// fields,
// attaches,
// expectedStatus: HttpStatusCode.BAD_REQUEST_400
// })
// })
// We don't check the extension yet
// it('Should fail with an invalid captionfile extension and octet-stream mime type', async function () {
// await createVideoCaption({
// url: server.url,
// accessToken: server.accessToken,
// language: 'zh',
// videoId: video.uuid,
// fixture: 'subtitle-bad.txt',
// mimeType: 'application/octet-stream',
// expectedStatus: HttpStatusCode.BAD_REQUEST_400
// })
// })
it('Should succeed with a valid captionfile extension and octet-stream mime type', async function () {
await server.captions.add({
language: 'zh',
videoId: video.uuid,
fixture: 'subtitle-good.srt',
mimeType: 'application/octet-stream'
})
})
// We don't check the file validity yet
// it('Should fail with an invalid captionfile srt', async function () {
// const attaches = {
// 'captionfile': buildAbsoluteFixturePath('subtitle-bad.srt')
// }
//
// const captionPath = path + video.uuid + '/captions/fr'
// await makeUploadRequest({
// method: 'PUT',
// url: server.url,
// path: captionPath,
// token: server.accessToken,
// fields,
// attaches,
// expectedStatus: HttpStatusCode.INTERNAL_SERVER_ERROR_500
// })
// })
it('Should success with the correct parameters', async function () {
const captionPath = path + video.uuid + '/captions/fr'
await makeUploadRequest({
method: 'PUT',
url: server.url,
path: captionPath,
token: server.accessToken,
fields,
attaches,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When listing video captions', function () {
it('Should fail without a valid uuid', async function () {
await makeGetRequest({ url: server.url, path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions' })
})
it('Should fail with an unknown id', async function () {
await makeGetRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a private video without token', async function () {
await makeGetRequest({
url: server.url,
path: path + privateVideo.shortUUID + '/captions',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path: path + privateVideo.shortUUID + '/captions',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 })
await makeGetRequest({
url: server.url,
path: path + privateVideo.shortUUID + '/captions',
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When deleting video caption', function () {
it('Should fail without a valid uuid', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions/fr',
token: server.accessToken
})
})
it('Should fail with an unknown id', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/fr',
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an invalid language', async function () {
await makeDeleteRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/16',
token: server.accessToken
})
})
it('Should fail with a missing language', async function () {
const captionPath = path + video.shortUUID + '/captions'
await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
})
it('Should fail with an unknown language', async function () {
const captionPath = path + video.shortUUID + '/captions/15'
await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
})
it('Should fail without access token', async function () {
const captionPath = path + video.shortUUID + '/captions/fr'
await makeDeleteRequest({ url: server.url, path: captionPath, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a bad access token', async function () {
const captionPath = path + video.shortUUID + '/captions/fr'
await makeDeleteRequest({ url: server.url, path: captionPath, token: 'coucou', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user', async function () {
const captionPath = path + video.shortUUID + '/captions/fr'
await makeDeleteRequest({
url: server.url,
path: captionPath,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should success with the correct parameters', async function () {
const captionPath = path + video.shortUUID + '/captions/fr'
await makeDeleteRequest({
url: server.url,
path: captionPath,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+319
ファイルの表示
@@ -0,0 +1,319 @@
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { HttpStatusCode, VideoChannelSyncCreate } from '@peertube/peertube-models'
import {
ChannelSyncsCommand,
createSingleServer,
makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test video channel sync API validator', () => {
const path = '/api/v1/video-channel-syncs'
let server: PeerTubeServer
let command: ChannelSyncsCommand
let rootChannelId: number
let rootChannelSyncId: number
const userInfo = {
accessToken: '',
username: 'user1',
id: -1,
channelId: -1,
syncId: -1
}
async function withChannelSyncDisabled<T> (callback: () => Promise<T>): Promise<void> {
try {
await server.config.disableChannelSync()
await callback()
} finally {
await server.config.enableChannelSync()
}
}
async function withMaxSyncsPerUser<T> (maxSync: number, callback: () => Promise<T>): Promise<void> {
const origConfig = await server.config.getCustomConfig()
await server.config.updateExistingConfig({
newConfig: {
import: {
videoChannelSynchronization: {
maxPerUser: maxSync
}
}
}
})
try {
await callback()
} finally {
await server.config.updateCustomConfig({ newCustomConfig: origConfig })
}
}
before(async function () {
this.timeout(30_000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
command = server.channelSyncs
rootChannelId = server.store.channel.id
{
userInfo.accessToken = await server.users.generateUserAndToken(userInfo.username)
const { videoChannels, id: userId } = await server.users.getMyInfo({ token: userInfo.accessToken })
userInfo.id = userId
userInfo.channelId = videoChannels[0].id
}
await server.config.enableChannelSync()
})
describe('When creating a sync', function () {
let baseCorrectParams: VideoChannelSyncCreate
before(function () {
baseCorrectParams = {
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelId: rootChannelId
}
})
it('Should fail when sync is disabled', async function () {
await withChannelSyncDisabled(async () => {
await command.create({
token: server.accessToken,
attributes: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with no authentication', async function () {
await command.create({
token: null,
attributes: baseCorrectParams,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail without a target url', async function () {
const attributes: VideoChannelSyncCreate = {
...baseCorrectParams,
externalChannelUrl: null
}
await command.create({
token: server.accessToken,
attributes,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail without a channelId', async function () {
const attributes: VideoChannelSyncCreate = {
...baseCorrectParams,
videoChannelId: null
}
await command.create({
token: server.accessToken,
attributes,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a channelId referring nothing', async function () {
const attributes: VideoChannelSyncCreate = {
...baseCorrectParams,
videoChannelId: 42
}
await command.create({
token: server.accessToken,
attributes,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail to create a sync when the user does not own the channel', async function () {
await command.create({
token: userInfo.accessToken,
attributes: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to create a sync with root and for another user\'s channel', async function () {
const { videoChannelSync } = await command.create({
token: server.accessToken,
attributes: {
...baseCorrectParams,
videoChannelId: userInfo.channelId
},
expectedStatus: HttpStatusCode.OK_200
})
userInfo.syncId = videoChannelSync.id
})
it('Should succeed with the correct parameters', async function () {
const { videoChannelSync } = await command.create({
token: server.accessToken,
attributes: baseCorrectParams,
expectedStatus: HttpStatusCode.OK_200
})
rootChannelSyncId = videoChannelSync.id
})
it('Should fail when the user exceeds allowed number of synchronizations', async function () {
await withMaxSyncsPerUser(1, async () => {
await command.create({
token: server.accessToken,
attributes: {
...baseCorrectParams,
videoChannelId: userInfo.channelId
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
})
describe('When listing my channel syncs', function () {
const myPath = '/api/v1/accounts/root/video-channel-syncs'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, myPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, myPath, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, myPath, server.accessToken)
})
it('Should succeed with the correct parameters', async function () {
await command.listByAccount({
accountName: 'root',
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail with no authentication', async function () {
await command.listByAccount({
accountName: 'root',
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail when a simple user lists another user\'s synchronizations', async function () {
await command.listByAccount({
accountName: 'root',
token: userInfo.accessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed when root lists another user\'s synchronizations', async function () {
await command.listByAccount({
accountName: userInfo.username,
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should succeed even with synchronization disabled', async function () {
await withChannelSyncDisabled(async function () {
await command.listByAccount({
accountName: 'root',
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
})
})
describe('When triggering deletion', function () {
it('should fail with no authentication', async function () {
await command.delete({
channelSyncId: userInfo.syncId,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail when channelSyncId does not refer to any sync', async function () {
await command.delete({
channelSyncId: 42,
token: server.accessToken,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail when sync is not owned by the user', async function () {
await command.delete({
channelSyncId: rootChannelSyncId,
token: userInfo.accessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed when root delete a sync they do not own', async function () {
await command.delete({
channelSyncId: userInfo.syncId,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should succeed when user delete a sync they own', async function () {
const { videoChannelSync } = await command.create({
attributes: {
externalChannelUrl: FIXTURE_URLS.youtubeChannel,
videoChannelId: userInfo.channelId
},
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
await command.delete({
channelSyncId: videoChannelSync.id,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should succeed even when synchronization is disabled', async function () {
await withChannelSyncDisabled(async function () {
await command.delete({
channelSyncId: rootChannelSyncId,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
})
after(async function () {
await server?.kill()
})
})
+379
ファイルの表示
@@ -0,0 +1,379 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, VideoChannelUpdate } from '@peertube/peertube-models'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
ChannelsCommand,
cleanupTests,
createSingleServer,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
makeUploadRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test video channels API validator', function () {
const videoChannelPath = '/api/v1/video-channels'
let server: PeerTubeServer
const userInfo = {
accessToken: '',
channelName: 'fake_channel',
id: -1,
videoQuota: -1,
videoQuotaDaily: -1
}
let command: ChannelsCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const userCreds = {
username: 'fake',
password: 'fake_password'
}
{
const user = await server.users.create({ username: userCreds.username, password: userCreds.password })
userInfo.id = user.id
userInfo.accessToken = await server.login.getAccessToken(userCreds)
}
command = server.channels
})
describe('When listing a video channels', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
})
})
describe('When listing account video channels', function () {
const accountChannelPath = '/api/v1/accounts/fake/video-channels'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, accountChannelPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, accountChannelPath, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, accountChannelPath, server.accessToken)
})
it('Should fail with a unknown account', async function () {
await server.channels.listByAccount({ accountName: 'unknown', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path: accountChannelPath,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When adding a video channel', function () {
const baseCorrectParams = {
name: 'super_channel',
displayName: 'hello',
description: 'super description',
support: 'super support text'
}
it('Should fail with a non authenticated user', async function () {
await makePostBodyRequest({
url: server.url,
path: videoChannelPath,
token: 'none',
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail without a name', async function () {
const fields = omit(baseCorrectParams, [ 'name' ])
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail with a bad name', async function () {
const fields = { ...baseCorrectParams, name: 'super name' }
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail without a name', async function () {
const fields = omit(baseCorrectParams, [ 'displayName' ])
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, displayName: 'super'.repeat(25) }
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(201) }
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
})
it('Should succeed with the correct parameters', async function () {
await makePostBodyRequest({
url: server.url,
path: videoChannelPath,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail when adding a channel with the same username', async function () {
await makePostBodyRequest({
url: server.url,
path: videoChannelPath,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
})
describe('When updating a video channel', function () {
const baseCorrectParams: VideoChannelUpdate = {
displayName: 'hello',
description: 'super description',
support: 'toto',
bulkVideosSupportUpdate: false
}
let path: string
before(async function () {
path = videoChannelPath + '/super_channel'
})
it('Should fail with a non authenticated user', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: 'hi',
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another authenticated user', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: userInfo.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, displayName: 'super'.repeat(25) }
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(201) }
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad bulkVideosSupportUpdate field', async function () {
const fields = { ...baseCorrectParams, bulkVideosSupportUpdate: 'super' }
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should succeed with the correct parameters', async function () {
await makePutBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When updating video channel avatars/banners', function () {
const types = [ 'avatar', 'banner' ]
let path: string
before(async function () {
path = videoChannelPath + '/super_channel'
})
it('Should fail with an incorrect input file', async function () {
for (const type of types) {
const fields = {}
const attaches = {
[type + 'file']: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
}
})
it('Should fail with a big file', async function () {
for (const type of types) {
const fields = {}
const attaches = {
[type + 'file']: buildAbsoluteFixturePath('avatar-big.png')
}
await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
}
})
it('Should fail with an unauthenticated user', async function () {
for (const type of types) {
const fields = {}
const attaches = {
[type + 'file']: buildAbsoluteFixturePath('avatar.png')
}
await makeUploadRequest({
url: server.url,
path: `${path}/${type}/pick`,
fields,
attaches,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should succeed with the correct params', async function () {
for (const type of types) {
const fields = {}
const attaches = {
[type + 'file']: buildAbsoluteFixturePath('avatar.png')
}
await makeUploadRequest({
url: server.url,
path: `${path}/${type}/pick`,
token: server.accessToken,
fields,
attaches,
expectedStatus: HttpStatusCode.OK_200
})
}
})
})
describe('When getting a video channel', function () {
it('Should return the list of the video channels with nothing', async function () {
const res = await makeGetRequest({
url: server.url,
path: videoChannelPath,
expectedStatus: HttpStatusCode.OK_200
})
expect(res.body.data).to.be.an('array')
})
it('Should return 404 with an incorrect video channel', async function () {
await makeGetRequest({
url: server.url,
path: videoChannelPath + '/super_channel2',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
path: videoChannelPath + '/super_channel',
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When getting channel followers', function () {
const path = '/api/v1/video-channels/super_channel/followers'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with a unauthenticated user', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a another user', async function () {
await makeGetRequest({ url: server.url, path, token: userInfo.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When deleting a video channel', function () {
it('Should fail with a non authenticated user', async function () {
await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another authenticated user', async function () {
await command.delete({ token: userInfo.accessToken, channelName: 'super_channel', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an unknown video channel id', async function () {
await command.delete({ channelName: 'super_channel2', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct parameters', async function () {
await command.delete({ channelName: 'super_channel' })
})
it('Should fail to delete the last user video channel', async function () {
await command.delete({ channelName: 'root_channel', expectedStatus: HttpStatusCode.CONFLICT_409 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+172
ファイルの表示
@@ -0,0 +1,172 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, Video, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test videos chapters API validator', function () {
let server: PeerTubeServer
let video: VideoCreateResult
let live: Video
let privateVideo: VideoCreateResult
let userAccessToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(60000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
video = await server.videos.upload()
privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
userAccessToken = await server.users.generateUserAndToken('user1')
await server.config.enableLive({ allowReplay: false })
const res = await server.live.quickCreate({ saveReplay: false, permanentLive: false })
live = res.video
})
describe('When updating chapters', function () {
it('Should fail without a valid uuid', async function () {
await server.chapters.update({ videoId: '4da6fd', chapters: [], expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown id', async function () {
await server.chapters.update({
videoId: 'ce0801ef-7124-48df-9b22-b473ace78797',
chapters: [],
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail without access token', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [],
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad access token', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [],
token: 'toto',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a another user access token', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [],
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a wrong chapters param', async function () {
await server.chapters.update({
videoId: video.id,
chapters: 'hello' as any,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad chapter title', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: '', timecode: 21 } ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: 'a'.repeat(150), timecode: 21 } ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad timecode', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: 'title', timecode: -5 } ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: 'title', timecode: 'hi' as any } ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with non unique timecodes', async function () {
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: 'title', timecode: 22 }, { title: 'hello', timecode: 21 } ],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to create chapters on a live', async function () {
await server.chapters.update({
videoId: live.id,
chapters: [],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct params', async function () {
await server.chapters.update({
videoId: video.id,
chapters: []
})
await server.chapters.update({
videoId: video.id,
chapters: [ { title: 'hello', timecode: 21 }, { title: 'hello 2', timecode: 35 } ]
})
})
})
describe('When listing chapters', function () {
it('Should fail without a valid uuid', async function () {
await server.chapters.list({ videoId: '4da6fd', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown id', async function () {
await server.chapters.list({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not list private chapters to anyone', async function () {
await server.chapters.list({ videoId: privateVideo.uuid, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should not list private chapters to another user', async function () {
await server.chapters.list({ videoId: privateVideo.uuid, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should list chapters', async function () {
await server.chapters.list({ videoId: privateVideo.uuid })
await server.chapters.list({ videoId: video.uuid })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+576
ファイルの表示
@@ -0,0 +1,576 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, VideoCommentPolicy, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { expect } from 'chai'
describe('Test video comments API validator', function () {
let pathThread: string
let pathComment: string
let server: PeerTubeServer
let video: VideoCreateResult
let userAccessToken: string
let userAccessToken2: string
let commentId: number
let privateCommentId: number
let privateVideo: VideoCreateResult
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
{
video = await server.videos.upload({ attributes: {} })
pathThread = '/api/v1/videos/' + video.uuid + '/comment-threads'
}
{
privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
}
{
const created = await server.comments.createThread({ videoId: video.uuid, text: 'coucou' })
commentId = created.id
pathComment = '/api/v1/videos/' + video.uuid + '/comments/' + commentId
}
{
const created = await server.comments.createThread({ videoId: privateVideo.uuid, text: 'coucou' })
privateCommentId = created.id
}
{
const user = { username: 'user1', password: 'my super password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
}
{
const user = { username: 'user2', password: 'my super password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken2 = await server.login.getAccessToken(user)
}
})
describe('When listing video comment threads', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, pathThread, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, pathThread, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, pathThread, server.accessToken)
})
it('Should fail with an incorrect video', async function () {
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a private video without token', async function () {
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When listing comments of a thread', function () {
it('Should fail with an incorrect video', async function () {
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads/' + commentId,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an incorrect thread id', async function () {
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/156',
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a private video without token', async function () {
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should success with the correct params', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
expectedStatus: HttpStatusCode.OK_200
})
await makeGetRequest({
url: server.url,
path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/' + commentId,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When adding a video thread', function () {
it('Should fail with a non authenticated user', async function () {
const fields = {
text: 'text'
}
await makePostBodyRequest({
url: server.url,
path: pathThread,
token: 'none',
fields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
})
it('Should fail with a short comment', async function () {
const fields = {
text: ''
}
await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
})
it('Should fail with a long comment', async function () {
const fields = {
text: 'h'.repeat(10001)
}
await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
})
it('Should fail with an incorrect video', async function () {
const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads'
const fields = { text: 'super comment' }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a private video of another user', async function () {
const fields = { text: 'super comment' }
await makePostBodyRequest({
url: server.url,
path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
token: userAccessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct parameters', async function () {
const fields = { text: 'super comment' }
await makePostBodyRequest({
url: server.url,
path: pathThread,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When adding a comment to a thread', function () {
it('Should fail with a non authenticated user', async function () {
const fields = {
text: 'text'
}
await makePostBodyRequest({
url: server.url,
path: pathComment,
token: 'none',
fields,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
})
it('Should fail with a short comment', async function () {
const fields = {
text: ''
}
await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
})
it('Should fail with a long comment', async function () {
const fields = {
text: 'h'.repeat(10001)
}
await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
})
it('Should fail with an incorrect video', async function () {
const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comments/' + commentId
const fields = {
text: 'super comment'
}
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a private video of another user', async function () {
const fields = { text: 'super comment' }
await makePostBodyRequest({
url: server.url,
path: '/api/v1/videos/' + privateVideo.uuid + '/comments/' + privateCommentId,
token: userAccessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an incorrect comment', async function () {
const path = '/api/v1/videos/' + video.uuid + '/comments/124'
const fields = {
text: 'super comment'
}
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should succeed with the correct parameters', async function () {
const fields = {
text: 'super comment'
}
await makePostBodyRequest({
url: server.url,
path: pathComment,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When removing video comments', function () {
it('Should fail with a non authenticated user', async function () {
await makeDeleteRequest({ url: server.url, path: pathComment, token: 'none', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user', async function () {
await makeDeleteRequest({
url: server.url,
path: pathComment,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an incorrect video', async function () {
const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comments/' + commentId
await makeDeleteRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an incorrect comment', async function () {
const path = '/api/v1/videos/' + video.uuid + '/comments/124'
await makeDeleteRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the same user', async function () {
let commentToDelete: number
{
const created = await server.comments.createThread({ videoId: video.uuid, token: userAccessToken, text: 'hello' })
commentToDelete = created.id
}
const path = '/api/v1/videos/' + video.uuid + '/comments/' + commentToDelete
await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeDeleteRequest({ url: server.url, path, token: userAccessToken, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
})
it('Should succeed with the owner of the video', async function () {
let commentToDelete: number
let anotherVideoUUID: string
{
const { uuid } = await server.videos.upload({ token: userAccessToken, attributes: { name: 'video' } })
anotherVideoUUID = uuid
}
{
const created = await server.comments.createThread({ videoId: anotherVideoUUID, text: 'hello' })
commentToDelete = created.id
}
const path = '/api/v1/videos/' + anotherVideoUUID + '/comments/' + commentToDelete
await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeDeleteRequest({ url: server.url, path, token: userAccessToken, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
})
it('Should succeed with the correct parameters', async function () {
await makeDeleteRequest({
url: server.url,
path: pathComment,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When a video has comments disabled', function () {
before(async function () {
video = await server.videos.upload({ attributes: { commentsPolicy: VideoCommentPolicy.DISABLED } })
pathThread = `/api/v1/videos/${video.uuid}/comment-threads`
})
it('Should return an empty thread list', async function () {
const res = await makeGetRequest({
url: server.url,
path: pathThread,
expectedStatus: HttpStatusCode.OK_200
})
expect(res.body.total).to.equal(0)
expect(res.body.data).to.have.lengthOf(0)
})
it('Should return an thread comments list')
it('Should return conflict on thread add', async function () {
const fields = {
text: 'super comment'
}
await makePostBodyRequest({
url: server.url,
path: pathThread,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should return conflict on comment thread add')
})
describe('When listing admin/user comments', function () {
const paths = [ '/api/v1/videos/comments', '/api/v1/users/me/videos/comments' ]
it('Should fail with a bad start/count pagination of invalid sort', async function () {
for (const path of paths) {
await checkBadStartPagination(server.url, path, server.accessToken)
await checkBadCountPagination(server.url, path, server.accessToken)
await checkBadSortPagination(server.url, path, server.accessToken)
}
})
it('Should fail with a non authenticated user', async function () {
await server.comments.listForAdmin({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await server.comments.listCommentsOnMyVideos({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail to list admin comments with a non admin user', async function () {
await server.comments.listForAdmin({ token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an invalid video', async function () {
await server.comments.listForAdmin({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.comments.listCommentsOnMyVideos({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.comments.listForAdmin({ videoId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await server.comments.listCommentsOnMyVideos({ videoId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an invalid channel', async function () {
await server.comments.listForAdmin({ videoChannelId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.comments.listCommentsOnMyVideos({ videoChannelId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.comments.listForAdmin({ videoChannelId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await server.comments.listCommentsOnMyVideos({ videoChannelId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail to list comments on my videos with non owned video or channel', async function () {
await server.comments.listCommentsOnMyVideos({
videoId: video.uuid,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
await server.comments.listCommentsOnMyVideos({
videoChannelId: server.store.channel.id,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
const base = {
search: 'toto',
searchAccount: 'toto',
searchVideo: 'toto',
videoId: video.uuid,
videoChannelId: server.store.channel.id,
autoTagOneOf: [ 'external-link' ]
}
await server.comments.listForAdmin({ ...base, isLocal: false })
await server.comments.listCommentsOnMyVideos(base)
})
})
describe('When approving a comment', function () {
let videoId: string
let commentId: number
let deletedCommentId: number
let userAccessToken3: string
before(async function () {
userAccessToken3 = await server.users.generateUserAndToken('user3')
{
const res = await server.videos.upload({
token: userAccessToken,
attributes: {
name: 'review policy',
commentsPolicy: VideoCommentPolicy.REQUIRES_APPROVAL
}
})
videoId = res.uuid
}
{
const res = await server.comments.createThread({ text: 'thread', videoId, token: userAccessToken2 })
commentId = res.id
}
{
const res = await server.comments.createThread({ text: 'deleted', videoId, token: userAccessToken2 })
deletedCommentId = res.id
await server.comments.delete({ commentId: deletedCommentId, videoId })
}
})
it('Should fail with a non authenticated user', async function () {
await server.comments.approve({ token: 'none', commentId, videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user', async function () {
await server.comments.approve({ token: userAccessToken3, commentId, videoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with the owner', async function () {
await server.comments.approve({ token: userAccessToken2, commentId, videoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an incorrect video', async function () {
await server.comments.approve({ token: userAccessToken, commentId, videoId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an incorrect comment', async function () {
await server.comments.approve({ token: userAccessToken, commentId: 42, videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a deleted comment', async function () {
await server.comments.approve({
token: userAccessToken,
commentId: deletedCommentId,
videoId,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should succeed with the correct params', async function () {
await server.comments.approve({ token: userAccessToken, commentId, videoId })
})
it('Should fail with an already held for review comment', async function () {
await server.comments.approve({ token: userAccessToken, commentId, videoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+195
ファイルの表示
@@ -0,0 +1,195 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { getAllFiles } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserRole, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test videos files', function () {
let servers: PeerTubeServer[]
let userToken: string
let moderatorToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(300_000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
})
describe('Getting metadata', function () {
let video: VideoDetails
before(async function () {
const { uuid } = await servers[0].videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
video = await servers[0].videos.getWithToken({ id: uuid })
})
it('Should not get metadata of private video without token', async function () {
for (const file of getAllFiles(video)) {
await makeRawRequest({ url: file.metadataUrl, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}
})
it('Should not get metadata of private video without the appropriate token', async function () {
for (const file of getAllFiles(video)) {
await makeRawRequest({ url: file.metadataUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should get metadata of private video with the appropriate token', async function () {
for (const file of getAllFiles(video)) {
await makeRawRequest({ url: file.metadataUrl, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 })
}
})
})
describe('Deleting files', function () {
let webVideoId: string
let hlsId: string
let remoteId: string
let validId1: string
let validId2: string
let hlsFileId: number
let webVideoFileId: number
let remoteHLSFileId: number
let remoteWebVideoFileId: number
before(async function () {
this.timeout(300_000)
{
const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
await waitJobs(servers)
const video = await servers[1].videos.get({ id: uuid })
remoteId = video.uuid
remoteHLSFileId = video.streamingPlaylists[0].files[0].id
remoteWebVideoFileId = video.files[0].id
}
{
await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' })
await waitJobs(servers)
const video = await servers[0].videos.get({ id: uuid })
validId1 = video.uuid
hlsFileId = video.streamingPlaylists[0].files[0].id
webVideoFileId = video.files[0].id
}
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'both 2' })
validId2 = uuid
}
}
await waitJobs(servers)
{
await servers[0].config.enableTranscoding({ hls: true, webVideo: false })
const { uuid } = await servers[0].videos.quickUpload({ name: 'hls' })
hlsId = uuid
}
await waitJobs(servers)
{
await servers[0].config.enableTranscoding({ webVideo: true, hls: false })
const { uuid } = await servers[0].videos.quickUpload({ name: 'web-video' })
webVideoId = uuid
}
await waitJobs(servers)
})
it('Should not delete files of a unknown video', async function () {
const expectedStatus = HttpStatusCode.NOT_FOUND_404
await servers[0].videos.removeHLSPlaylist({ videoId: 404, expectedStatus })
await servers[0].videos.removeAllWebVideoFiles({ videoId: 404, expectedStatus })
await servers[0].videos.removeHLSFile({ videoId: 404, fileId: hlsFileId, expectedStatus })
await servers[0].videos.removeWebVideoFile({ videoId: 404, fileId: webVideoFileId, expectedStatus })
})
it('Should not delete unknown files', async function () {
const expectedStatus = HttpStatusCode.NOT_FOUND_404
await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: webVideoFileId, expectedStatus })
await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: hlsFileId, expectedStatus })
})
it('Should not delete files of a remote video', async function () {
const expectedStatus = HttpStatusCode.BAD_REQUEST_400
await servers[0].videos.removeHLSPlaylist({ videoId: remoteId, expectedStatus })
await servers[0].videos.removeAllWebVideoFiles({ videoId: remoteId, expectedStatus })
await servers[0].videos.removeHLSFile({ videoId: remoteId, fileId: remoteHLSFileId, expectedStatus })
await servers[0].videos.removeWebVideoFile({ videoId: remoteId, fileId: remoteWebVideoFileId, expectedStatus })
})
it('Should not delete files by a non admin user', async function () {
const expectedStatus = HttpStatusCode.FORBIDDEN_403
await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: userToken, expectedStatus })
await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: moderatorToken, expectedStatus })
await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1, token: userToken, expectedStatus })
await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1, token: moderatorToken, expectedStatus })
await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: userToken, expectedStatus })
await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: moderatorToken, expectedStatus })
await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId, token: userToken, expectedStatus })
await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId, token: moderatorToken, expectedStatus })
})
it('Should not delete files if the files are not available', async function () {
await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].videos.removeAllWebVideoFiles({ videoId: webVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not delete files if no both versions are available', async function () {
await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].videos.removeAllWebVideoFiles({ videoId: webVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should delete files if both versions are available', async function () {
await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId })
await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId })
await servers[0].videos.removeHLSPlaylist({ videoId: validId1 })
await servers[0].videos.removeAllWebVideoFiles({ videoId: validId2 })
})
})
after(async function () {
await cleanupTests(servers)
})
})
+439
ファイルの表示
@@ -0,0 +1,439 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, VideoCommentPolicy, VideoImportCreate, VideoPrivacy } from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
makeGetRequest,
makePostBodyRequest,
makeUploadRequest,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
describe('Test video imports API validator', function () {
const path = '/api/v1/videos/imports'
let server: PeerTubeServer
let userAccessToken = ''
let channelId: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
const username = 'user1'
const password = 'my super password'
await server.users.create({ username, password })
userAccessToken = await server.login.getAccessToken({ username, password })
{
const { videoChannels } = await server.users.getMyInfo()
channelId = videoChannels[0].id
}
})
describe('When listing my video imports', function () {
const myPath = '/api/v1/users/me/videos/imports'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, myPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, myPath, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, myPath, server.accessToken)
})
it('Should fail with a bad videoChannelSyncId param', async function () {
await makeGetRequest({
url: server.url,
path: myPath,
query: { videoChannelSyncId: 'toto' },
token: server.accessToken
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path: myPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
})
})
describe('When adding a video import', function () {
let baseCorrectParams: VideoImportCreate
before(function () {
baseCorrectParams = {
targetUrl: FIXTURE_URLS.goodVideo,
name: 'my super name',
category: 5,
licence: 1,
language: 'pt',
nsfw: false,
commentsPolicy: VideoCommentPolicy.ENABLED,
downloadEnabled: true,
waitTranscoding: true,
description: 'my super description',
support: 'my super support text',
tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC,
channelId
}
})
it('Should fail with nothing', async function () {
const fields = {}
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail without a target url', async function () {
const fields = omit(baseCorrectParams, [ 'targetUrl' ])
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad target url', async function () {
const fields = { ...baseCorrectParams, targetUrl: 'htt://hello' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with localhost', async function () {
const fields = { ...baseCorrectParams, targetUrl: 'http://localhost:8000' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a private IP target urls', async function () {
const targetUrls = [
'http://127.0.0.1:8000',
'http://127.0.0.1',
'http://127.0.0.1/hello',
'https://192.168.1.42',
'http://192.168.1.42',
'http://127.0.0.1.cpy.re'
]
for (const targetUrl of targetUrls) {
const fields = { ...baseCorrectParams, targetUrl }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad category', async function () {
const fields = { ...baseCorrectParams, category: 125 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad licence', async function () {
const fields = { ...baseCorrectParams, licence: 125 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad language', async function () {
const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad commentsPolicy', async function () {
const fields = { ...baseCorrectParams, commentsPolicy: 42 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail without a channel', async function () {
const fields = omit(baseCorrectParams, [ 'channelId' ])
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a bad channel', async function () {
const fields = { ...baseCorrectParams, channelId: 545454 }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with another user channel', async function () {
const user = {
username: 'fake',
password: 'fake_password'
}
await server.users.create({ username: user.username, password: user.password })
const accessTokenUser = await server.login.getAccessToken(user)
const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
const customChannelId = videoChannels[0].id
const fields = { ...baseCorrectParams, channelId: customChannelId }
await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
})
it('Should fail with too many tags', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a tag length too low', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a tag length too big', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with an incorrect thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with a big thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with an incorrect preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with a big preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with an invalid torrent file', async function () {
const fields = omit(baseCorrectParams, [ 'targetUrl' ])
const attaches = {
torrentfile: buildAbsoluteFixturePath('avatar-big.png')
}
await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
})
it('Should fail with an invalid magnet URI', async function () {
let fields = omit(baseCorrectParams, [ 'targetUrl' ])
fields = { ...fields, magnetUri: 'blabla' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should succeed with the correct parameters', async function () {
this.timeout(120000)
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should forbid to import http videos', async function () {
await server.config.updateExistingConfig({
newConfig: {
import: {
videos: {
http: {
enabled: false
},
torrent: {
enabled: true
}
}
}
}
})
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields: baseCorrectParams,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
it('Should forbid to import torrent videos', async function () {
await server.config.updateExistingConfig({
newConfig: {
import: {
videos: {
http: {
enabled: true
},
torrent: {
enabled: false
}
}
}
}
})
let fields = omit(baseCorrectParams, [ 'targetUrl' ])
fields = { ...fields, magnetUri: FIXTURE_URLS.magnet }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.CONFLICT_409
})
fields = omit(fields, [ 'magnetUri' ])
const attaches = {
torrentfile: buildAbsoluteFixturePath('video-720p.torrent')
}
await makeUploadRequest({
url: server.url,
path,
token: server.accessToken,
fields,
attaches,
expectedStatus: HttpStatusCode.CONFLICT_409
})
})
})
describe('Deleting/cancelling a video import', function () {
let importId: number
async function importVideo () {
const attributes = { channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
const res = await server.videoImports.importVideo({ attributes })
return res.id
}
before(async function () {
importId = await importVideo()
})
it('Should fail with an invalid import id', async function () {
await server.videoImports.cancel({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await server.videoImports.delete({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown import id', async function () {
await server.videoImports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await server.videoImports.delete({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail without token', async function () {
await server.videoImports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await server.videoImports.delete({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user token', async function () {
await server.videoImports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await server.videoImports.delete({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail to cancel non pending import', async function () {
this.timeout(60000)
await waitJobs([ server ])
await server.videoImports.cancel({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
})
it('Should succeed to delete an import', async function () {
await server.videoImports.delete({ importId })
})
it('Should fail to delete a pending import', async function () {
await server.jobs.pauseJobQueue()
importId = await importVideo()
await server.videoImports.delete({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
})
it('Should succeed to cancel an import', async function () {
importId = await importVideo()
await server.videoImports.cancel({ importId })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+605
ファイルの表示
@@ -0,0 +1,605 @@
import {
HttpStatusCode,
HttpStatusCodeType,
PeerTubeProblemDocument,
ServerErrorCode,
VideoCommentPolicy,
VideoCreateResult,
VideoPrivacy
} from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
makePostBodyRequest,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { checkUploadVideoParam } from '@tests/shared/videos.js'
import { expect } from 'chai'
describe('Test video passwords validator', function () {
let path: string
let server: PeerTubeServer
let userAccessToken = ''
let video: VideoCreateResult
let channelId: number
let publicVideo: VideoCreateResult
let commentId: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(50000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await server.config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
latencySetting: {
enabled: false
},
allowReplay: false
},
import: {
videos: {
http:{
enabled: true
}
}
}
}
})
userAccessToken = await server.users.generateUserAndToken('user1')
{
const body = await server.users.getMyInfo()
channelId = body.videoChannels[0].id
}
{
video = await server.videos.quickUpload({
name: 'password protected video',
privacy: VideoPrivacy.PASSWORD_PROTECTED,
videoPasswords: [ 'password1', 'password2' ]
})
}
path = '/api/v1/videos/'
})
async function checkVideoPasswordOptions (options: {
server: PeerTubeServer
token: string
videoPasswords: string[]
expectedStatus: HttpStatusCodeType
mode: 'uploadLegacy' | 'uploadResumable' | 'import' | 'updateVideo' | 'updatePasswords' | 'live'
}) {
const { server, token, videoPasswords, expectedStatus = HttpStatusCode.OK_200, mode } = options
const attaches = {
fixture: buildAbsoluteFixturePath('video_short.webm')
}
const baseCorrectParams = {
name: 'my super name',
category: 5,
licence: 1,
language: 'pt',
nsfw: false,
commentsPolicy: VideoCommentPolicy.ENABLED,
downloadEnabled: true,
waitTranscoding: true,
description: 'my super description',
support: 'my super support text',
tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PASSWORD_PROTECTED,
channelId,
originallyPublishedAt: new Date().toISOString()
}
if (mode === 'uploadLegacy') {
const fields = { ...baseCorrectParams, videoPasswords }
return checkUploadVideoParam({ server, token, attributes: { ...fields, ...attaches }, expectedStatus, mode: 'legacy' })
}
if (mode === 'uploadResumable') {
const fields = { ...baseCorrectParams, videoPasswords }
return checkUploadVideoParam({ server, token, attributes: { ...fields, ...attaches }, expectedStatus, mode: 'resumable' })
}
if (mode === 'import') {
const attributes = { ...baseCorrectParams, targetUrl: FIXTURE_URLS.goodVideo, videoPasswords }
return server.videoImports.importVideo({ attributes, expectedStatus })
}
if (mode === 'updateVideo') {
const attributes = { ...baseCorrectParams, videoPasswords }
return server.videos.update({ token, expectedStatus, id: video.id, attributes })
}
if (mode === 'updatePasswords') {
return server.videoPasswords.updateAll({ token, expectedStatus, videoId: video.id, passwords: videoPasswords })
}
if (mode === 'live') {
const fields = { ...baseCorrectParams, videoPasswords }
return server.live.create({ fields, expectedStatus })
}
}
function validateVideoPasswordList (mode: 'uploadLegacy' | 'uploadResumable' | 'import' | 'updateVideo' | 'updatePasswords' | 'live') {
it('Should fail with a password protected privacy without providing a password', async function () {
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords: undefined,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
it('Should fail with a password protected privacy and an empty password list', async function () {
const videoPasswords = []
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
it('Should fail with a password protected privacy and a too short password', async function () {
const videoPasswords = [ 'p' ]
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
it('Should fail with a password protected privacy and a too long password', async function () {
const videoPasswords = [ 'Very very very very very very very very very very very very very very very very very very long password' ]
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
it('Should fail with a password protected privacy and an empty password', async function () {
const videoPasswords = [ '' ]
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
it('Should fail with a password protected privacy and duplicated passwords', async function () {
const videoPasswords = [ 'password', 'password' ]
await checkVideoPasswordOptions({
server,
token: server.accessToken,
videoPasswords,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
})
})
if (mode === 'updatePasswords') {
it('Should fail for an unauthenticated user', async function () {
const videoPasswords = [ 'password' ]
await checkVideoPasswordOptions({
server,
token: null,
videoPasswords,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
mode
})
})
it('Should fail for an unauthorized user', async function () {
const videoPasswords = [ 'password' ]
await checkVideoPasswordOptions({
server,
token: userAccessToken,
videoPasswords,
expectedStatus: HttpStatusCode.FORBIDDEN_403,
mode
})
})
}
it('Should succeed with a password protected privacy and correct passwords', async function () {
const videoPasswords = [ 'password1', 'password2' ]
const expectedStatus = mode === 'updatePasswords' || mode === 'updateVideo'
? HttpStatusCode.NO_CONTENT_204
: HttpStatusCode.OK_200
await checkVideoPasswordOptions({ server, token: server.accessToken, videoPasswords, expectedStatus, mode })
})
}
describe('When adding or updating a video', function () {
describe('Resumable upload', function () {
validateVideoPasswordList('uploadResumable')
})
describe('Legacy upload', function () {
validateVideoPasswordList('uploadLegacy')
})
describe('When importing a video', function () {
validateVideoPasswordList('import')
})
describe('When updating a video', function () {
validateVideoPasswordList('updateVideo')
})
describe('When updating the password list of a video', function () {
validateVideoPasswordList('updatePasswords')
})
describe('When creating a live', function () {
validateVideoPasswordList('live')
})
})
async function checkVideoAccessOptions (options: {
server: PeerTubeServer
token?: string
videoPassword?: string
expectedStatus: HttpStatusCodeType
mode: 'get' | 'getWithPassword' | 'getWithToken' | 'listCaptions' | 'createThread' | 'listThreads' | 'replyThread' | 'rate' | 'token'
}) {
const { server, token = null, videoPassword, expectedStatus, mode } = options
if (mode === 'get') {
return server.videos.get({ id: video.id, expectedStatus })
}
if (mode === 'getWithToken') {
return server.videos.getWithToken({
id: video.id,
token,
expectedStatus
})
}
if (mode === 'getWithPassword') {
return server.videos.getWithPassword({
id: video.id,
token,
expectedStatus,
password: videoPassword
})
}
if (mode === 'rate') {
return server.videos.rate({
id: video.id,
token,
expectedStatus,
rating: 'like',
videoPassword
})
}
if (mode === 'createThread') {
const fields = { text: 'super comment' }
const headers = videoPassword !== undefined && videoPassword !== null
? { 'x-peertube-video-password': videoPassword }
: undefined
const body = await makePostBodyRequest({
url: server.url,
path: path + video.uuid + '/comment-threads',
token,
fields,
headers,
expectedStatus
})
return JSON.parse(body.text)
}
if (mode === 'replyThread') {
const fields = { text: 'super reply' }
const headers = videoPassword !== undefined && videoPassword !== null
? { 'x-peertube-video-password': videoPassword }
: undefined
return makePostBodyRequest({
url: server.url,
path: path + video.uuid + '/comments/' + commentId,
token,
fields,
headers,
expectedStatus
})
}
if (mode === 'listThreads') {
return server.comments.listThreads({
videoId: video.id,
token,
expectedStatus,
videoPassword
})
}
if (mode === 'listCaptions') {
return server.captions.list({
videoId: video.id,
token,
expectedStatus,
videoPassword
})
}
if (mode === 'token') {
return server.videoToken.create({
videoId: video.id,
token,
expectedStatus,
videoPassword
})
}
}
function checkVideoError (error: any, mode: 'providePassword' | 'incorrectPassword') {
const serverCode = mode === 'providePassword'
? ServerErrorCode.VIDEO_REQUIRES_PASSWORD
: ServerErrorCode.INCORRECT_VIDEO_PASSWORD
const message = mode === 'providePassword'
? 'Please provide a password to access this password protected video'
: 'Incorrect video password. Access to the video is denied.'
if (!error.code) {
error = JSON.parse(error.text)
}
expect(error.code).to.equal(serverCode)
expect(error.detail).to.equal(message)
expect(error.error).to.equal(message)
expect(error.status).to.equal(HttpStatusCode.FORBIDDEN_403)
}
function validateVideoAccess (mode: 'get' | 'listCaptions' | 'createThread' | 'listThreads' | 'replyThread' | 'rate' | 'token') {
const requiresUserAuth = [ 'createThread', 'replyThread', 'rate' ].includes(mode)
let tokens: string[]
if (!requiresUserAuth) {
it('Should fail without providing a password for an unlogged user', async function () {
const body = await checkVideoAccessOptions({ server, expectedStatus: HttpStatusCode.FORBIDDEN_403, mode })
const error = body as unknown as PeerTubeProblemDocument
checkVideoError(error, 'providePassword')
})
}
it('Should fail without providing a password for an unauthorised user', async function () {
const tmp = mode === 'get' ? 'getWithToken' : mode
const body = await checkVideoAccessOptions({
server,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403,
mode: tmp
})
const error = body as unknown as PeerTubeProblemDocument
checkVideoError(error, 'providePassword')
})
it('Should fail if a wrong password is entered', async function () {
const tmp = mode === 'get' ? 'getWithPassword' : mode
tokens = [ userAccessToken, server.accessToken ]
if (!requiresUserAuth) tokens.push(null)
for (const token of tokens) {
const body = await checkVideoAccessOptions({
server,
token,
videoPassword: 'toto',
expectedStatus: HttpStatusCode.FORBIDDEN_403,
mode: tmp
})
const error = body as unknown as PeerTubeProblemDocument
checkVideoError(error, 'incorrectPassword')
}
})
it('Should fail if an empty password is entered', async function () {
const tmp = mode === 'get' ? 'getWithPassword' : mode
for (const token of tokens) {
const body = await checkVideoAccessOptions({
server,
token,
videoPassword: '',
expectedStatus: HttpStatusCode.FORBIDDEN_403,
mode: tmp
})
const error = body as unknown as PeerTubeProblemDocument
checkVideoError(error, 'incorrectPassword')
}
})
it('Should fail if an inccorect password containing the correct password is entered', async function () {
const tmp = mode === 'get' ? 'getWithPassword' : mode
for (const token of tokens) {
const body = await checkVideoAccessOptions({
server,
token,
videoPassword: 'password11',
expectedStatus: HttpStatusCode.FORBIDDEN_403,
mode: tmp
})
const error = body as unknown as PeerTubeProblemDocument
checkVideoError(error, 'incorrectPassword')
}
})
it('Should succeed without providing a password for an authorised user', async function () {
const tmp = mode === 'get' ? 'getWithToken' : mode
const expectedStatus = mode === 'rate' ? HttpStatusCode.NO_CONTENT_204 : HttpStatusCode.OK_200
const body = await checkVideoAccessOptions({ server, token: server.accessToken, expectedStatus, mode: tmp })
if (mode === 'createThread') commentId = body.comment.id
})
it('Should succeed using correct passwords', async function () {
const tmp = mode === 'get' ? 'getWithPassword' : mode
const expectedStatus = mode === 'rate' ? HttpStatusCode.NO_CONTENT_204 : HttpStatusCode.OK_200
for (const token of tokens) {
await checkVideoAccessOptions({ server, videoPassword: 'password1', token, expectedStatus, mode: tmp })
await checkVideoAccessOptions({ server, videoPassword: 'password2', token, expectedStatus, mode: tmp })
}
})
}
describe('When accessing password protected video', function () {
describe('For getting a password protected video', function () {
validateVideoAccess('get')
})
describe('For rating a video', function () {
validateVideoAccess('rate')
})
describe('For creating a thread', function () {
validateVideoAccess('createThread')
})
describe('For replying to a thread', function () {
validateVideoAccess('replyThread')
})
describe('For listing threads', function () {
validateVideoAccess('listThreads')
})
describe('For getting captions', function () {
validateVideoAccess('listCaptions')
})
describe('For creating video file token', function () {
validateVideoAccess('token')
})
})
describe('When listing passwords', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
})
it('Should fail for unauthenticated user', async function () {
await server.videoPasswords.list({
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
videoId: video.id
})
})
it('Should fail for unauthorized user', async function () {
await server.videoPasswords.list({
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403,
videoId: video.id
})
})
it('Should succeed with the correct parameters', async function () {
await server.videoPasswords.list({
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200,
videoId: video.id
})
})
})
describe('When deleting a password', async function () {
const passwords = (await server.videoPasswords.list({ videoId: video.id })).data
it('Should fail with wrong password id', async function () {
await server.videoPasswords.remove({ id: -1, videoId: video.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail for unauthenticated user', async function () {
await server.videoPasswords.remove({
id: passwords[0].id,
token: null,
videoId: video.id,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail for unauthorized user', async function () {
await server.videoPasswords.remove({
id: passwords[0].id,
token: userAccessToken,
videoId: video.id,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail for non password protected video', async function () {
publicVideo = await server.videos.quickUpload({ name: 'public video' })
await server.videoPasswords.remove({ id: passwords[0].id, videoId: publicVideo.id, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail for password not linked to correct video', async function () {
const video2 = await server.videos.quickUpload({
name: 'password protected video',
privacy: VideoPrivacy.PASSWORD_PROTECTED,
videoPasswords: [ 'password1', 'password2' ]
})
await server.videoPasswords.remove({ id: passwords[0].id, videoId: video2.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with correct parameter', async function () {
await server.videoPasswords.remove({ id: passwords[0].id, videoId: video.id, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
})
it('Should fail for last password of a video', async function () {
await server.videoPasswords.remove({ id: passwords[1].id, videoId: video.id, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+695
ファイルの表示
@@ -0,0 +1,695 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import {
HttpStatusCode,
VideoPlaylistCreate,
VideoPlaylistCreateResult,
VideoPlaylistElementCreate,
VideoPlaylistElementUpdate,
VideoPlaylistPrivacy,
VideoPlaylistReorder,
VideoPlaylistType
} from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
PlaylistsCommand,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test video playlists API validator', function () {
let server: PeerTubeServer
let userAccessToken: string
let playlist: VideoPlaylistCreateResult
let privatePlaylistUUID: string
let watchLaterPlaylistId: number
let videoId: number
let elementId: number
let command: PlaylistsCommand
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
userAccessToken = await server.users.generateUserAndToken('user1')
videoId = (await server.videos.quickUpload({ name: 'video 1' })).id
command = server.playlists
{
const { data } = await command.listByAccount({
token: server.accessToken,
handle: 'root',
start: 0,
count: 5,
playlistType: VideoPlaylistType.WATCH_LATER
})
watchLaterPlaylistId = data[0].id
}
{
playlist = await command.create({
attributes: {
displayName: 'super playlist',
privacy: VideoPlaylistPrivacy.PUBLIC,
videoChannelId: server.store.channel.id
}
})
}
{
const created = await command.create({
attributes: {
displayName: 'private',
privacy: VideoPlaylistPrivacy.PRIVATE
}
})
privatePlaylistUUID = created.uuid
}
})
describe('When listing playlists', function () {
const globalPath = '/api/v1/video-playlists'
const accountPath = '/api/v1/accounts/root/video-playlists'
const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, globalPath, server.accessToken)
await checkBadStartPagination(server.url, accountPath, server.accessToken)
await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, globalPath, server.accessToken)
await checkBadCountPagination(server.url, accountPath, server.accessToken)
await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, globalPath, server.accessToken)
await checkBadSortPagination(server.url, accountPath, server.accessToken)
await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
})
it('Should fail with a bad playlist type', async function () {
await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } })
await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } })
await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } })
})
it('Should fail with a bad account parameter', async function () {
const accountPath = '/api/v1/accounts/root2/video-playlists'
await makeGetRequest({
url: server.url,
path: accountPath,
expectedStatus: HttpStatusCode.NOT_FOUND_404,
token: server.accessToken
})
})
it('Should fail with a bad video channel parameter', async function () {
const accountPath = '/api/v1/video-channels/bad_channel/video-playlists'
await makeGetRequest({
url: server.url,
path: accountPath,
expectedStatus: HttpStatusCode.NOT_FOUND_404,
token: server.accessToken
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path: globalPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
await makeGetRequest({ url: server.url, path: accountPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
await makeGetRequest({
url: server.url,
path: videoChannelPath,
expectedStatus: HttpStatusCode.OK_200,
token: server.accessToken
})
})
})
describe('When listing videos of a playlist', function () {
const path = '/api/v1/video-playlists/'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path: path + playlist.shortUUID + '/videos', expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When getting a video playlist', function () {
it('Should fail with a bad id or uuid', async function () {
await command.get({ playlistId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an unknown playlist', async function () {
await command.get({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail to get an unlisted playlist with the number id', async function () {
const playlist = await command.create({
attributes: {
displayName: 'super playlist',
videoChannelId: server.store.channel.id,
privacy: VideoPlaylistPrivacy.UNLISTED
}
})
await command.get({ playlistId: playlist.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should succeed with the correct params', async function () {
await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When creating/updating a video playlist', function () {
const getBase = (
attributes?: Partial<VideoPlaylistCreate>,
wrapper?: Partial<Parameters<PlaylistsCommand['create']>[0]>
) => {
return {
attributes: {
displayName: 'display name',
privacy: VideoPlaylistPrivacy.UNLISTED,
thumbnailfile: 'custom-thumbnail.jpg',
videoChannelId: server.store.channel.id,
...attributes
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
...wrapper
}
}
const getUpdate = (params: any, playlistId: number | string) => {
return { ...params, playlistId }
}
it('Should fail with an unauthenticated user', async function () {
const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail without displayName', async function () {
const params = getBase({ displayName: undefined })
await command.create(params)
})
it('Should fail with an incorrect display name', async function () {
const params = getBase({ displayName: 's'.repeat(300) })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail with an incorrect description', async function () {
const params = getBase({ description: 't' })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail with an incorrect privacy', async function () {
const params = getBase({ privacy: 45 as any })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail with an unknown video channel id', async function () {
const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail with an incorrect thumbnail file', async function () {
const params = getBase({ thumbnailfile: 'video_short.mp4' })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail with a thumbnail file too big', async function () {
const params = getBase({ thumbnailfile: 'custom-preview-big.png' })
await command.create(params)
await command.update(getUpdate(params, playlist.shortUUID))
})
it('Should fail to set "public" a playlist not assigned to a channel', async function () {
const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: undefined })
const params2 = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: 'null' as any })
const params3 = getBase({ privacy: undefined, videoChannelId: 'null' as any })
await command.create(params)
await command.create(params2)
await command.update(getUpdate(params, privatePlaylistUUID))
await command.update(getUpdate(params2, playlist.shortUUID))
await command.update(getUpdate(params3, playlist.shortUUID))
})
it('Should fail with an unknown playlist to update', async function () {
await command.update(getUpdate(
getBase({}, { expectedStatus: HttpStatusCode.NOT_FOUND_404 }),
42
))
})
it('Should fail to update a playlist of another user', async function () {
await command.update(getUpdate(
getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }),
playlist.shortUUID
))
})
it('Should fail to update the watch later playlist', async function () {
await command.update(getUpdate(
getBase({}, { expectedStatus: HttpStatusCode.BAD_REQUEST_400 }),
watchLaterPlaylistId
))
})
it('Should succeed with the correct params', async function () {
{
const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
await command.create(params)
}
{
const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
await command.update(getUpdate(params, playlist.shortUUID))
}
})
})
describe('When adding an element in a playlist', function () {
const getBase = (
attributes?: Partial<VideoPlaylistElementCreate>,
wrapper?: Partial<Parameters<PlaylistsCommand['addElement']>[0]>
) => {
return {
attributes: {
videoId,
startTimestamp: 2,
stopTimestamp: 3,
...attributes
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
playlistId: playlist.id,
...wrapper
}
}
it('Should fail with an unauthenticated user', async function () {
const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.addElement(params)
})
it('Should fail with the playlist of another user', async function () {
const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await command.addElement(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
{
const params = getBase({}, { playlistId: 'toto' })
await command.addElement(params)
}
{
const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.addElement(params)
}
})
it('Should fail with an unknown or incorrect video id', async function () {
const params = getBase({ videoId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.addElement(params)
})
it('Should fail with a bad start/stop timestamp', async function () {
{
const params = getBase({ startTimestamp: -42 })
await command.addElement(params)
}
{
const params = getBase({ stopTimestamp: 'toto' as any })
await command.addElement(params)
}
})
it('Succeed with the correct params', async function () {
const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
const created = await command.addElement(params)
elementId = created.id
})
})
describe('When updating an element in a playlist', function () {
const getBase = (
attributes?: Partial<VideoPlaylistElementUpdate>,
wrapper?: Partial<Parameters<PlaylistsCommand['updateElement']>[0]>
) => {
return {
attributes: {
startTimestamp: 1,
stopTimestamp: 2,
...attributes
},
elementId,
playlistId: playlist.id,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
...wrapper
}
}
it('Should fail with an unauthenticated user', async function () {
const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.updateElement(params)
})
it('Should fail with the playlist of another user', async function () {
const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await command.updateElement(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
{
const params = getBase({}, { playlistId: 'toto' })
await command.updateElement(params)
}
{
const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.updateElement(params)
}
})
it('Should fail with an unknown or incorrect playlistElement id', async function () {
{
const params = getBase({}, { elementId: 'toto' })
await command.updateElement(params)
}
{
const params = getBase({}, { elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.updateElement(params)
}
})
it('Should fail with a bad start/stop timestamp', async function () {
{
const params = getBase({ startTimestamp: 'toto' as any })
await command.updateElement(params)
}
{
const params = getBase({ stopTimestamp: -42 })
await command.updateElement(params)
}
})
it('Should fail with an unknown element', async function () {
const params = getBase({}, { elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.updateElement(params)
})
it('Succeed with the correct params', async function () {
const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
await command.updateElement(params)
})
})
describe('When reordering elements of a playlist', function () {
let videoId3: number
let videoId4: number
const getBase = (
attributes?: Partial<VideoPlaylistReorder>,
wrapper?: Partial<Parameters<PlaylistsCommand['reorderElements']>[0]>
) => {
return {
attributes: {
startPosition: 1,
insertAfterPosition: 2,
reorderLength: 3,
...attributes
},
playlistId: playlist.shortUUID,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
...wrapper
}
}
before(async function () {
videoId3 = (await server.videos.quickUpload({ name: 'video 3' })).id
videoId4 = (await server.videos.quickUpload({ name: 'video 4' })).id
for (const id of [ videoId3, videoId4 ]) {
await command.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: id } })
}
})
it('Should fail with an unauthenticated user', async function () {
const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.reorderElements(params)
})
it('Should fail with the playlist of another user', async function () {
const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await command.reorderElements(params)
})
it('Should fail with an invalid playlist', async function () {
{
const params = getBase({}, { playlistId: 'toto' })
await command.reorderElements(params)
}
{
const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.reorderElements(params)
}
})
it('Should fail with an invalid start position', async function () {
{
const params = getBase({ startPosition: -1 })
await command.reorderElements(params)
}
{
const params = getBase({ startPosition: 'toto' as any })
await command.reorderElements(params)
}
{
const params = getBase({ startPosition: 42 })
await command.reorderElements(params)
}
})
it('Should fail with an invalid insert after position', async function () {
{
const params = getBase({ insertAfterPosition: 'toto' as any })
await command.reorderElements(params)
}
{
const params = getBase({ insertAfterPosition: -2 })
await command.reorderElements(params)
}
{
const params = getBase({ insertAfterPosition: 42 })
await command.reorderElements(params)
}
})
it('Should fail with an invalid reorder length', async function () {
{
const params = getBase({ reorderLength: 'toto' as any })
await command.reorderElements(params)
}
{
const params = getBase({ reorderLength: -2 })
await command.reorderElements(params)
}
{
const params = getBase({ reorderLength: 42 })
await command.reorderElements(params)
}
})
it('Succeed with the correct params', async function () {
const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
await command.reorderElements(params)
})
})
describe('When checking exists in playlist endpoint', function () {
const path = '/api/v1/users/me/video-playlists/videos-exist'
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({
url: server.url,
path,
query: { videoIds: [ 1, 2 ] },
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with invalid video ids', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: { videoIds: 'toto' }
})
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: { videoIds: [ 'toto' ] }
})
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: { videoIds: [ 1, 'toto' ] }
})
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: { videoIds: [ 1, 2 ] },
expectedStatus: HttpStatusCode.OK_200
})
})
})
describe('When deleting an element in a playlist', function () {
const getBase = (wrapper: Partial<Parameters<PlaylistsCommand['removeElement']>[0]>) => {
return {
elementId,
playlistId: playlist.uuid,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
...wrapper
}
}
it('Should fail with an unauthenticated user', async function () {
const params = getBase({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.removeElement(params)
})
it('Should fail with the playlist of another user', async function () {
const params = getBase({ token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await command.removeElement(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
{
const params = getBase({ playlistId: 'toto' })
await command.removeElement(params)
}
{
const params = getBase({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.removeElement(params)
}
})
it('Should fail with an unknown or incorrect video id', async function () {
{
const params = getBase({ elementId: 'toto' as any })
await command.removeElement(params)
}
{
const params = getBase({ elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.removeElement(params)
}
})
it('Should fail with an unknown element', async function () {
const params = getBase({ elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await command.removeElement(params)
})
it('Succeed with the correct params', async function () {
const params = getBase({ expectedStatus: HttpStatusCode.NO_CONTENT_204 })
await command.removeElement(params)
})
})
describe('When deleting a playlist', function () {
it('Should fail with an unknown playlist', async function () {
await command.delete({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a playlist of another user', async function () {
await command.delete({ token: userAccessToken, playlistId: playlist.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with the watch later playlist', async function () {
await command.delete({ playlistId: watchLaterPlaylistId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct params', async function () {
await command.delete({ playlistId: playlist.uuid })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+247
ファイルの表示
@@ -0,0 +1,247 @@
import { HttpStatusCode, VideoSource } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
makeRawRequest,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video sources API validator', function () {
let server: PeerTubeServer = null
let uuid: string
let userToken: string
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
userToken = await server.users.generateUserAndToken('user1')
})
describe('When getting latest source', function () {
before(async function () {
const created = await server.videos.quickUpload({ name: 'video' })
uuid = created.uuid
})
it('Should fail without a valid uuid', async function () {
await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should receive 404 when passing a non existing video id', async function () {
await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not get the source as unauthenticated', async function () {
await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null })
})
it('Should not get the source with another user', async function () {
await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: userToken })
})
it('Should succeed with the correct parameters get the source as another user', async function () {
await server.videos.getSource({ id: uuid })
})
})
describe('When updating source video file', function () {
let userAccessToken: string
let userId: number
let videoId: string
let userVideoId: string
before(async function () {
const res = await server.users.generate('user2')
userAccessToken = res.token
userId = res.userId
const { uuid } = await server.videos.quickUpload({ name: 'video' })
videoId = uuid
await waitJobs([ server ])
})
it('Should fail if not enabled on the instance', async function () {
await server.config.disableFileUpdate()
await server.videos.replaceSourceFile({ videoId, fixture: 'video_short.mp4', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail on an unknown video', async function () {
await server.config.enableFileUpdate()
await server.videos.replaceSourceFile({ videoId: 404, fixture: 'video_short.mp4', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with an invalid video', async function () {
await server.config.enableLive({ allowReplay: false })
const { video } = await server.live.quickCreate({ saveReplay: false, permanentLive: true })
await server.videos.replaceSourceFile({
videoId: video.uuid,
fixture: 'video_short.mp4',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail without token', async function () {
await server.videos.replaceSourceFile({
token: null,
videoId,
fixture: 'video_short.mp4',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user', async function () {
await server.videos.replaceSourceFile({
token: userAccessToken,
videoId,
fixture: 'video_short.mp4',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an incorrect input file', async function () {
await server.videos.replaceSourceFile({
fixture: 'video_short_fake.webm',
videoId,
completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
})
await server.videos.replaceSourceFile({
fixture: 'video_short.mkv',
videoId,
expectedStatus: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415
})
})
it('Should fail if quota is exceeded', async function () {
this.timeout(60000)
const { uuid } = await server.videos.quickUpload({ name: 'user video' })
userVideoId = uuid
await waitJobs([ server ])
await server.users.update({ userId, videoQuota: 1 })
await server.videos.replaceSourceFile({
token: userAccessToken,
videoId: uuid,
fixture: 'video_short.mp4',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct params', async function () {
this.timeout(60000)
await server.users.update({ userId, videoQuota: 1000 * 1000 * 1000 })
await server.videos.replaceSourceFile({ videoId: userVideoId, fixture: 'video_short.mp4' })
})
})
describe('When downloading the source file', function () {
let videoFileToken: string
let videoId: string
let source: VideoSource
let user3: string
let user4: string
before(async function () {
this.timeout(60000)
user3 = await server.users.generateUserAndToken('user3')
user4 = await server.users.generateUserAndToken('user4')
await server.config.enableMinimumTranscoding({ hls: true, keepOriginal: true, webVideo: true })
const { uuid } = await server.videos.quickUpload({ name: 'video', token: user3 })
videoId = uuid
videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid, token: user3 })
await waitJobs([ server ])
source = await server.videos.getSource({ id: videoId, token: user3 })
})
it('Should fail with an invalid filename', async function () {
await makeRawRequest({ url: server.url + '/download/original-video-files/hello.mp4', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail without header token or video file token', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an invalid header token', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, token: 'toto', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with an invalid video file token', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, query: { videoFileToken: 'toto' }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with header token of another user', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, token: user4, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with video file token of another user', async function () {
const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid, token: user4 })
await makeRawRequest({ url: source.fileDownloadUrl, query: { videoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should succeed with a valid header token', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, token: user3, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should succeed with a valid header token', async function () {
await makeRawRequest({ url: source.fileDownloadUrl, query: { videoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When deleting video source file', function () {
let userAccessToken: string
let videoId: string
before(async function () {
userAccessToken = await server.users.generateUserAndToken('user56')
await server.config.enableMinimumTranscoding({ keepOriginal: true })
const { uuid } = await server.videos.quickUpload({ name: 'with source' })
videoId = uuid
await waitJobs([ server ])
})
it('Should fail without token', async function () {
await server.videos.deleteSource({ id: videoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another user', async function () {
await server.videos.deleteSource({ id: videoId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with an unknown video', async function () {
await server.videos.deleteSource({ id: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should succeed with the correct params', async function () {
await server.videos.deleteSource({ id: videoId })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+45
ファイルの表示
@@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands'
describe('Test video storyboards API validator', function () {
let server: PeerTubeServer
let publicVideo: { uuid: string }
let privateVideo: { uuid: string }
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
publicVideo = await server.videos.quickUpload({ name: 'public' })
privateVideo = await server.videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE })
})
it('Should fail without a valid uuid', async function () {
await server.storyboard.list({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should receive 404 when passing a non existing video id', async function () {
await server.storyboard.list({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not get the private storyboard without the appropriate token', async function () {
await server.storyboard.list({ id: privateVideo.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null })
await server.storyboard.list({ id: publicVideo.uuid, expectedStatus: HttpStatusCode.OK_200, token: null })
})
it('Should succeed with the correct parameters', async function () {
await server.storyboard.list({ id: privateVideo.uuid })
await server.storyboard.list({ id: publicVideo.uuid })
})
after(async function () {
await cleanupTests([ server ])
})
})
+392
ファイルの表示
@@ -0,0 +1,392 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, HttpStatusCodeType, VideoStudioTask } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
VideoStudioCommand,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video studio API validator', function () {
let server: PeerTubeServer
let command: VideoStudioCommand
let userAccessToken: string
let videoUUID: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(120_000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
userAccessToken = await server.users.generateUserAndToken('user1')
await server.config.enableMinimumTranscoding()
const { uuid } = await server.videos.quickUpload({ name: 'video' })
videoUUID = uuid
command = server.videoStudio
await waitJobs([ server ])
})
describe('Task creation', function () {
describe('Config settings', function () {
it('Should fail if studio is disabled', async function () {
await server.config.updateExistingConfig({
newConfig: {
videoStudio: {
enabled: false
}
}
})
await command.createEditionTasks({
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to enable studio if transcoding is disabled', async function () {
await server.config.updateExistingConfig({
newConfig: {
videoStudio: {
enabled: true
},
transcoding: {
enabled: false
}
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed to enable video studio', async function () {
await server.config.updateExistingConfig({
newConfig: {
videoStudio: {
enabled: true
},
transcoding: {
enabled: true
}
}
})
})
})
describe('Common tasks', function () {
it('Should fail without token', async function () {
await command.createEditionTasks({
token: null,
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await command.createEditionTasks({
token: userAccessToken,
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid video', async function () {
await command.createEditionTasks({
videoId: 'tintin',
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown video', async function () {
await command.createEditionTasks({
videoId: 42,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an already in transcoding state video', async function () {
this.timeout(60000)
const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
await waitJobs([ server ])
await server.jobs.pauseJobQueue()
await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
await command.createEditionTasks({
videoId: uuid,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.CONFLICT_409
})
await server.jobs.resumeJobQueue()
})
it('Should fail with a bad complex task', async function () {
await command.createEditionTasks({
videoId: videoUUID,
tasks: [
{
name: 'cut',
options: {
start: 1,
end: 2
}
},
{
name: 'hadock',
options: {
start: 1,
end: 2
}
}
] as any,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail without task', async function () {
await command.createEditionTasks({
videoId: videoUUID,
tasks: [],
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with too many tasks', async function () {
const tasks: VideoStudioTask[] = []
for (let i = 0; i < 110; i++) {
tasks.push({
name: 'cut',
options: {
start: 1
}
})
}
await command.createEditionTasks({
videoId: videoUUID,
tasks,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with correct parameters', async function () {
await server.jobs.pauseJobQueue()
await command.createEditionTasks({
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should fail with a video that is already waiting for edition', async function () {
this.timeout(360000)
await command.createEditionTasks({
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.CONFLICT_409
})
await server.jobs.resumeJobQueue()
await waitJobs([ server ])
})
})
describe('Cut task', function () {
async function cut (start: number, end: number, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
await command.createEditionTasks({
videoId: videoUUID,
tasks: [
{
name: 'cut',
options: {
start,
end
}
}
],
expectedStatus
})
}
it('Should fail with bad start/end', async function () {
const invalid = [
'tintin',
-1,
undefined
]
for (const value of invalid) {
await cut(value as any, undefined)
await cut(undefined, value as any)
}
})
it('Should fail with the same start/end', async function () {
await cut(2, 2)
})
it('Should fail with inconsistents start/end', async function () {
await cut(2, 1)
})
it('Should fail without start and end', async function () {
await cut(undefined, undefined)
})
it('Should succeed with the correct params', async function () {
this.timeout(360000)
await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
await waitJobs([ server ])
})
})
describe('Watermark task', function () {
async function addWatermark (file: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
await command.createEditionTasks({
videoId: videoUUID,
tasks: [
{
name: 'add-watermark',
options: {
file
}
}
],
expectedStatus
})
}
it('Should fail without waterkmark', async function () {
await addWatermark(undefined)
})
it('Should fail with an invalid watermark', async function () {
await addWatermark('video_short.mp4')
})
it('Should succeed with the correct params', async function () {
this.timeout(360000)
await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
await waitJobs([ server ])
})
})
describe('Intro/Outro task', function () {
async function addIntroOutro (
type: 'add-intro' | 'add-outro',
file: string,
expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400
) {
await command.createEditionTasks({
videoId: videoUUID,
tasks: [
{
name: type,
options: {
file
}
}
],
expectedStatus
})
}
it('Should fail without file', async function () {
await addIntroOutro('add-intro', undefined)
await addIntroOutro('add-outro', undefined)
})
it('Should fail with an invalid file', async function () {
await addIntroOutro('add-intro', 'custom-thumbnail.jpg')
await addIntroOutro('add-outro', 'custom-thumbnail.jpg')
})
it('Should fail with a file that does not contain video stream', async function () {
await addIntroOutro('add-intro', 'sample.ogg')
await addIntroOutro('add-outro', 'sample.ogg')
})
it('Should succeed with the correct params', async function () {
this.timeout(360000)
await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
await waitJobs([ server ])
await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
await waitJobs([ server ])
})
it('Should check total quota when creating the task', async function () {
this.timeout(360000)
const user = await server.users.create({ username: 'user_quota_1' })
const token = await server.login.getAccessToken('user_quota_1')
const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })
const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCodeType) => {
return command.createEditionTasks({
token,
videoId: uuid,
tasks: [
{
name: type,
options: {
file: 'video_short.mp4'
}
}
],
expectedStatus
})
}
await waitJobs([ server ])
const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })
// Still valid
await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)
await waitJobs([ server ])
// Too much quota
await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+70
ファイルの表示
@@ -0,0 +1,70 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands'
describe('Test video tokens', function () {
let server: PeerTubeServer
let privateVideoId: string
let passwordProtectedVideoId: string
let userToken: string
const videoPassword = 'password'
// ---------------------------------------------------------------
before(async function () {
this.timeout(300_000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
{
const { uuid } = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
privateVideoId = uuid
}
{
const { uuid } = await server.videos.quickUpload({
name: 'password protected video',
privacy: VideoPrivacy.PASSWORD_PROTECTED,
videoPasswords: [ videoPassword ]
})
passwordProtectedVideoId = uuid
}
userToken = await server.users.generateUserAndToken('user1')
})
it('Should not generate tokens on private video for unauthenticated user', async function () {
await server.videoToken.create({ videoId: privateVideoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should not generate tokens of unknown video', async function () {
await server.videoToken.create({ videoId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not generate tokens with incorrect password', async function () {
await server.videoToken.create({
videoId: passwordProtectedVideoId,
token: null,
expectedStatus: HttpStatusCode.FORBIDDEN_403,
videoPassword: 'incorrectPassword'
})
})
it('Should not generate tokens of a non owned video', async function () {
await server.videoToken.create({ videoId: privateVideoId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should generate token', async function () {
await server.videoToken.create({ videoId: privateVideoId })
})
it('Should generate token on password protected video', async function () {
await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword, token: null })
await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword, token: userToken })
await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword })
})
after(async function () {
await cleanupTests([ server ])
})
})
+106
ファイルの表示
@@ -0,0 +1,106 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createMultipleServers,
doubleFollow,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video transcription API validator', function () {
let servers: PeerTubeServer[]
let userToken: string
let anotherUserToken: string
let remoteId: string
let validId: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
anotherUserToken = await servers[0].users.generateUserAndToken('user2', UserRole.USER)
{
const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
remoteId = uuid
}
{
const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1', token: userToken })
validId = uuid
}
await waitJobs(servers)
await servers[0].config.enableTranscription()
})
it('Should not run transcription of an unknown video', async function () {
await servers[0].captions.runGenerate({ videoId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should not run transcription of a remote video', async function () {
await servers[0].captions.runGenerate({ videoId: remoteId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should not run transcription by a owner/moderator user', async function () {
await servers[0].captions.runGenerate({ videoId: validId, token: anotherUserToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should not run transcription if a caption file already exists', async function () {
await servers[0].captions.add({
language: 'en',
videoId: validId,
fixture: 'subtitle-good1.vtt'
})
await servers[0].captions.runGenerate({ videoId: validId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].captions.delete({ language: 'en', videoId: validId })
})
it('Should not run transcription if the instance disabled it', async function () {
await servers[0].config.disableTranscription()
await servers[0].captions.runGenerate({ videoId: validId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].config.enableTranscription()
})
it('Should succeed to run transcription', async function () {
await servers[0].captions.runGenerate({ videoId: validId, token: userToken })
})
it('Should fail to run transcription twice', async function () {
await servers[0].captions.runGenerate({ videoId: validId, token: userToken, expectedStatus: HttpStatusCode.CONFLICT_409 })
})
it('Should fail to run transcription twice with a non-admin user with the forceTranscription boolean', async function () {
await servers[0].captions.runGenerate({
videoId: validId,
token: userToken,
forceTranscription: true,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed to run transcription twice with the forceTranscription boolean', async function () {
await servers[0].captions.runGenerate({ videoId: validId, forceTranscription: true })
})
after(async function () {
await cleanupTests(servers)
})
})
+196
ファイルの表示
@@ -0,0 +1,196 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
HttpStatusCode,
HttpStatusCodeType,
UserRole,
VideoInclude,
VideoIncludeType,
VideoPrivacy,
VideoPrivacyType
} from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeGetRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test video filters validators', function () {
let server: PeerTubeServer
let userAccessToken: string
let moderatorAccessToken: string
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
const user = { username: 'user1', password: 'my super password' }
await server.users.create({ username: user.username, password: user.password })
userAccessToken = await server.login.getAccessToken(user)
const moderator = { username: 'moderator', password: 'my super password' }
await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
moderatorAccessToken = await server.login.getAccessToken(moderator)
})
describe('When setting video filters', function () {
const validIncludes = [
VideoInclude.NONE,
VideoInclude.BLOCKED_OWNER,
VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED,
VideoInclude.SOURCE
]
async function testEndpoints (options: {
token?: string
isLocal?: boolean
include?: VideoIncludeType
privacyOneOf?: VideoPrivacyType[]
autoTagOneOf?: string[]
expectedStatus: HttpStatusCodeType
excludeAlreadyWatched?: boolean
unauthenticatedUser?: boolean
filter?: string
}) {
const paths = [
'/api/v1/video-channels/root_channel/videos',
'/api/v1/accounts/root/videos',
'/api/v1/videos',
'/api/v1/search/videos'
]
for (const path of paths) {
const token = options.unauthenticatedUser
? undefined
: options.token || server.accessToken
await makeGetRequest({
url: server.url,
path,
token,
query: {
isLocal: options.isLocal,
privacyOneOf: options.privacyOneOf,
autoTagOneOf: options.autoTagOneOf,
include: options.include,
excludeAlreadyWatched: options.excludeAlreadyWatched,
filter: options.filter
},
expectedStatus: options.expectedStatus
})
}
}
it('Should fail with the old filter query param', async function () {
await testEndpoints({ filter: 'all-local', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a bad privacyOneOf', async function () {
await testEndpoints({ privacyOneOf: [ 'toto' ] as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a good privacyOneOf', async function () {
await testEndpoints({ privacyOneOf: [ VideoPrivacy.INTERNAL ], expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail to use privacyOneOf with a simple user', async function () {
await testEndpoints({
privacyOneOf: [ VideoPrivacy.INTERNAL ],
token: userAccessToken,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail to use autoTagOneOf with a simple user', async function () {
await testEndpoints({
autoTagOneOf: [ 'test' ],
token: userAccessToken,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should succeed to use autoTagOneOf with a moderator', async function () {
await testEndpoints({
autoTagOneOf: [ 'test' ],
token: moderatorAccessToken,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should fail with a bad include', async function () {
await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a good include', async function () {
for (const include of validIncludes) {
await testEndpoints({ include, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should fail to include more videos with a simple user', async function () {
for (const include of validIncludes) {
await testEndpoints({ token: userAccessToken, include, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}
})
it('Should succeed to list all local/all with a moderator', async function () {
for (const include of validIncludes) {
await testEndpoints({ token: moderatorAccessToken, include, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should succeed to list all local/all with an admin', async function () {
for (const include of validIncludes) {
await testEndpoints({ token: server.accessToken, include, expectedStatus: HttpStatusCode.OK_200 })
}
})
// Because we cannot authenticate the user on the RSS endpoint
it('Should fail on the feeds endpoint with the all filter', async function () {
for (const include of [ VideoInclude.NOT_PUBLISHED_STATE ]) {
await makeGetRequest({
url: server.url,
path: '/feeds/videos.json',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
query: {
include
}
})
}
})
it('Should succeed on the feeds endpoint with the local filter', async function () {
await makeGetRequest({
url: server.url,
path: '/feeds/videos.json',
expectedStatus: HttpStatusCode.OK_200,
query: {
isLocal: true
}
})
})
it('Should fail when trying to exclude already watched videos for an unlogged user', async function () {
await testEndpoints({ excludeAlreadyWatched: true, unauthenticatedUser: true, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed when trying to exclude already watched videos for a logged user', async function () {
await testEndpoints({ token: userAccessToken, excludeAlreadyWatched: true, expectedStatus: HttpStatusCode.OK_200 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+145
ファイルの表示
@@ -0,0 +1,145 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { checkBadCountPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePostBodyRequest,
makePutBodyRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
describe('Test videos history API validator', function () {
const myHistoryPath = '/api/v1/users/me/history/videos'
const myHistoryRemove = myHistoryPath + '/remove'
let viewPath: string
let server: PeerTubeServer
let videoId: number
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
const { id, uuid } = await server.videos.upload()
viewPath = '/api/v1/videos/' + uuid + '/views'
videoId = id
})
describe('When notifying a user is watching a video', function () {
it('Should fail with a bad token', async function () {
const fields = { currentTime: 5 }
await makePutBodyRequest({ url: server.url, path: viewPath, fields, token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should succeed with the correct parameters', async function () {
const fields = { currentTime: 5 }
await makePutBodyRequest({
url: server.url,
path: viewPath,
fields,
token: server.accessToken,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When listing user videos history', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, myHistoryPath, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, myHistoryPath, server.accessToken)
})
it('Should fail with an unauthenticated user', async function () {
await makeGetRequest({ url: server.url, path: myHistoryPath, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should succeed with the correct params', async function () {
await makeGetRequest({ url: server.url, token: server.accessToken, path: myHistoryPath, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When removing a specific user video history element', function () {
let path: string
before(function () {
path = myHistoryPath + '/' + videoId
})
it('Should fail with an unauthenticated user', async function () {
await makeDeleteRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a bad videoId parameter', async function () {
await makeDeleteRequest({
url: server.url,
token: server.accessToken,
path: myHistoryRemove + '/hi',
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await makeDeleteRequest({
url: server.url,
token: server.accessToken,
path,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When removing all user videos history', function () {
it('Should fail with an unauthenticated user', async function () {
await makePostBodyRequest({ url: server.url, path: myHistoryPath + '/remove', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a bad beforeDate parameter', async function () {
const body = { beforeDate: '15' }
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path: myHistoryRemove,
fields: body,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with a valid beforeDate param', async function () {
const body = { beforeDate: new Date().toISOString() }
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path: myHistoryRemove,
fields: body,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
it('Should succeed without body', async function () {
await makePostBodyRequest({
url: server.url,
token: server.accessToken,
path: myHistoryRemove,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+31
ファイルの表示
@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands'
describe('Test videos overview API validator', function () {
let server: PeerTubeServer
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
})
describe('When getting videos overview', function () {
it('Should fail with a bad pagination', async function () {
await server.overviews.getVideos({ page: 0, expectedStatus: 400 })
await server.overviews.getVideos({ page: 100, expectedStatus: 400 })
})
it('Should succeed with a good pagination', async function () {
await server.overviews.getVideos({ page: 1 })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+897
ファイルの表示
@@ -0,0 +1,897 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { omit, randomInt } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
PeerTubeProblemDocument,
VideoCommentPolicy,
VideoCreateResult,
VideoPrivacy
} from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
makeDeleteRequest,
makeGetRequest,
makePutBodyRequest,
makeUploadRequest,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
import { checkUploadVideoParam } from '@tests/shared/videos.js'
import { expect } from 'chai'
import { join } from 'path'
describe('Test videos API validator', function () {
const path = '/api/v1/videos/'
let server: PeerTubeServer
let userAccessToken = ''
let accountName: string
let channelId: number
let channelName: string
let video: VideoCreateResult
let privateVideo: VideoCreateResult
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
userAccessToken = await server.users.generateUserAndToken('user1')
{
const body = await server.users.getMyInfo()
channelId = body.videoChannels[0].id
channelName = body.videoChannels[0].name
accountName = body.account.name + '@' + body.account.host
}
{
privateVideo = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
}
})
describe('When listing videos', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path)
})
it('Should fail with a bad skipVideos query', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200, query: { skipCount: 'toto' } })
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200, query: { skipCount: false } })
})
})
describe('When searching a video', function () {
it('Should fail with nothing', async function () {
await makeGetRequest({
url: server.url,
path: join(path, 'search'),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, join(path, 'search', 'test'))
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, join(path, 'search', 'test'))
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, join(path, 'search', 'test'))
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When listing my videos', function () {
const path = '/api/v1/users/me/videos'
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should fail with an invalid channel', async function () {
await makeGetRequest({ url: server.url, token: server.accessToken, path, query: { channelId: 'toto' } })
})
it('Should fail with an unknown channel', async function () {
await makeGetRequest({
url: server.url,
token: server.accessToken,
path,
query: { channelId: 89898 },
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, token: server.accessToken, path, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When listing account videos', function () {
let path: string
before(async function () {
path = '/api/v1/accounts/' + accountName + '/videos'
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When listing video channel videos', function () {
let path: string
before(async function () {
path = '/api/v1/video-channels/' + channelName + '/videos'
})
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, path, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, path, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, path, server.accessToken)
})
it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('When adding a video', function () {
const baseCorrectParams = {
name: 'my super name',
category: 5,
licence: 1,
language: 'pt',
nsfw: false,
commentsPolicy: VideoCommentPolicy.ENABLED,
downloadEnabled: true,
waitTranscoding: true,
description: 'my super description',
support: 'my super support text',
tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC,
channelId: -1,
originallyPublishedAt: new Date().toISOString()
}
const baseCorrectAttaches = {
fixture: buildAbsoluteFixturePath('video_short.webm')
}
before(function () {
// Put in before to have channelId
baseCorrectParams.channelId = channelId
})
function runSuite (mode: 'legacy' | 'resumable') {
const baseOptions = () => {
return {
server,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
mode
}
}
it('Should fail with nothing', async function () {
const fields = {}
const attaches = {}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail without name', async function () {
const fields = omit(baseCorrectParams, [ 'name' ])
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad category', async function () {
const fields = { ...baseCorrectParams, category: 125 }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad licence', async function () {
const fields = { ...baseCorrectParams, licence: 125 }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad language', async function () {
const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with bad commentsPolicy', async function () {
const fields = { ...baseCorrectParams, commentsPolicy: 42 as any }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail without a channel', async function () {
const fields = omit(baseCorrectParams, [ 'channelId' ])
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad channel', async function () {
const fields = { ...baseCorrectParams, channelId: 545454 }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with another user channel', async function () {
const user = {
username: 'fake' + randomInt(0, 1500),
password: 'fake_password'
}
await server.users.create({ username: user.username, password: user.password })
const accessTokenUser = await server.login.getAccessToken(user)
const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
const customChannelId = videoChannels[0].id
const fields = { ...baseCorrectParams, channelId: customChannelId }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({
...baseOptions(),
token: userAccessToken,
attributes: { ...fields, ...attaches }
})
})
it('Should fail with too many tags', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a tag length too low', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a tag length too big', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad schedule update (miss updateAt)', async function () {
const fields = { ...baseCorrectParams, scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } as any }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad schedule update (wrong updateAt)', async function () {
const fields = {
...baseCorrectParams,
scheduleUpdate: {
privacy: VideoPrivacy.PUBLIC,
updateAt: 'toto'
}
}
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a bad originally published at attribute', async function () {
const fields = { ...baseCorrectParams, originallyPublishedAt: 'toto' }
const attaches = baseCorrectAttaches
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail without an input file', async function () {
const fields = baseCorrectParams
const attaches = {}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with an incorrect input file', async function () {
const fields = baseCorrectParams
let attaches = { fixture: buildAbsoluteFixturePath('video_short_fake.webm') }
await checkUploadVideoParam({
...baseOptions(),
attributes: { ...fields, ...attaches },
// 200 for the init request, 422 when the file has finished being uploaded
expectedStatus: undefined,
completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
})
attaches = { fixture: buildAbsoluteFixturePath('video_short.mkv') }
await checkUploadVideoParam({
...baseOptions(),
attributes: { ...fields, ...attaches },
expectedStatus: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415
})
})
it('Should fail with an incorrect thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('video_short.mp4'),
fixture: buildAbsoluteFixturePath('video_short.mp4')
}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a big thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png'),
fixture: buildAbsoluteFixturePath('video_short.mp4')
}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with an incorrect preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('video_short.mp4'),
fixture: buildAbsoluteFixturePath('video_short.mp4')
}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should fail with a big preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('custom-preview-big.png'),
fixture: buildAbsoluteFixturePath('video_short.mp4')
}
await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
})
it('Should report the appropriate error', async function () {
const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
const attaches = baseCorrectAttaches
const attributes = { ...fields, ...attaches }
const body = await checkUploadVideoParam({ ...baseOptions(), attributes })
const error = body as unknown as PeerTubeProblemDocument
if (mode === 'legacy') {
expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy')
} else {
expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit')
}
expect(error.type).to.equal('about:blank')
expect(error.title).to.equal('Bad Request')
expect(error.detail).to.equal('Incorrect request parameters: language')
expect(error.error).to.equal('Incorrect request parameters: language')
expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
expect(error['invalid-params'].language).to.exist
})
it('Should succeed with the correct parameters', async function () {
this.timeout(30000)
const fields = baseCorrectParams
{
const attaches = baseCorrectAttaches
await checkUploadVideoParam({
...baseOptions(),
attributes: { ...fields, ...attaches },
expectedStatus: HttpStatusCode.OK_200
})
}
{
const attaches = {
...baseCorrectAttaches,
videofile: buildAbsoluteFixturePath('video_short.mp4')
}
await checkUploadVideoParam({
...baseOptions(),
attributes: { ...fields, ...attaches },
expectedStatus: HttpStatusCode.OK_200
})
}
{
const attaches = {
...baseCorrectAttaches,
videofile: buildAbsoluteFixturePath('video_short.ogv')
}
await checkUploadVideoParam({
...baseOptions(),
attributes: { ...fields, ...attaches },
expectedStatus: HttpStatusCode.OK_200
})
}
})
}
describe('Resumable upload', function () {
runSuite('resumable')
})
describe('Legacy upload', function () {
runSuite('legacy')
})
})
describe('When updating a video', function () {
const baseCorrectParams = {
name: 'my super name',
category: 5,
licence: 2,
language: 'pt',
nsfw: false,
commentsPolicy: VideoCommentPolicy.DISABLED,
downloadEnabled: false,
description: 'my super description',
privacy: VideoPrivacy.PUBLIC,
tags: [ 'tag1', 'tag2' ]
}
before(async function () {
const { data } = await server.videos.list()
video = data[0]
})
it('Should fail with nothing', async function () {
const fields = {}
await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail without a valid uuid', async function () {
const fields = baseCorrectParams
await makePutBodyRequest({ url: server.url, path: path + 'blabla', token: server.accessToken, fields })
})
it('Should fail with an unknown id', async function () {
const fields = baseCorrectParams
await makePutBodyRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06',
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad category', async function () {
const fields = { ...baseCorrectParams, category: 125 }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad licence', async function () {
const fields = { ...baseCorrectParams, licence: 125 }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad language', async function () {
const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () {
const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a long support text', async function () {
const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad channel', async function () {
const fields = { ...baseCorrectParams, channelId: 545454 }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with too many tags', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a tag length too low', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a tag length too big', async function () {
const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad schedule update (miss updateAt)', async function () {
const fields = { ...baseCorrectParams, scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad schedule update (wrong updateAt)', async function () {
const fields = { ...baseCorrectParams, scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with a bad originally published at param', async function () {
const fields = { ...baseCorrectParams, originallyPublishedAt: 'toto' }
await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
})
it('Should fail with an incorrect thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({
url: server.url,
method: 'PUT',
path: path + video.shortUUID,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with a big thumbnail file', async function () {
const fields = baseCorrectParams
const attaches = {
thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({
url: server.url,
method: 'PUT',
path: path + video.shortUUID,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with an incorrect preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('video_short.mp4')
}
await makeUploadRequest({
url: server.url,
method: 'PUT',
path: path + video.shortUUID,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with a big preview file', async function () {
const fields = baseCorrectParams
const attaches = {
previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
}
await makeUploadRequest({
url: server.url,
method: 'PUT',
path: path + video.shortUUID,
token: server.accessToken,
fields,
attaches
})
})
it('Should fail with a video of another user without the appropriate right', async function () {
const fields = baseCorrectParams
await makePutBodyRequest({
url: server.url,
path: path + video.shortUUID,
token: userAccessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with a video of another server')
it('Shoud report the appropriate error', async function () {
const fields = { ...baseCorrectParams, licence: 125 }
const res = await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
const error = res.body as PeerTubeProblemDocument
expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo')
expect(error.type).to.equal('about:blank')
expect(error.title).to.equal('Bad Request')
expect(error.detail).to.equal('Incorrect request parameters: licence')
expect(error.error).to.equal('Incorrect request parameters: licence')
expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
expect(error['invalid-params'].licence).to.exist
})
it('Should succeed with the correct parameters', async function () {
const fields = baseCorrectParams
await makePutBodyRequest({
url: server.url,
path: path + video.shortUUID,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When getting a video', function () {
it('Should return the list of the videos with nothing', async function () {
const res = await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.OK_200
})
expect(res.body.data).to.be.an('array')
expect(res.body.data.length).to.equal(6)
})
it('Should fail without a correct uuid', async function () {
await server.videos.get({ id: 'coucou', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should return 404 with an incorrect video', async function () {
await server.videos.get({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Shoud report the appropriate error', async function () {
const body = await server.videos.get({ id: 'hi', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const error = body as unknown as PeerTubeProblemDocument
expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo')
expect(error.type).to.equal('about:blank')
expect(error.title).to.equal('Bad Request')
expect(error.detail).to.equal('Incorrect request parameters: id')
expect(error.error).to.equal('Incorrect request parameters: id')
expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
expect(error['invalid-params'].id).to.exist
})
it('Should succeed with the correct parameters', async function () {
await server.videos.get({ id: video.shortUUID })
})
})
describe('When rating a video', function () {
let videoId: number
before(async function () {
const { data } = await server.videos.list()
videoId = data[0].id
})
it('Should fail without a valid uuid', async function () {
const fields = {
rating: 'like'
}
await makePutBodyRequest({ url: server.url, path: path + 'blabla/rate', token: server.accessToken, fields })
})
it('Should fail with an unknown id', async function () {
const fields = {
rating: 'like'
}
await makePutBodyRequest({
url: server.url,
path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/rate',
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with a wrong rating', async function () {
const fields = {
rating: 'likes'
}
await makePutBodyRequest({ url: server.url, path: path + videoId + '/rate', token: server.accessToken, fields })
})
it('Should fail with a private video of another user', async function () {
const fields = {
rating: 'like'
}
await makePutBodyRequest({
url: server.url,
path: path + privateVideo.uuid + '/rate',
token: userAccessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should succeed with the correct parameters', async function () {
const fields = {
rating: 'like'
}
await makePutBodyRequest({
url: server.url,
path: path + videoId + '/rate',
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.NO_CONTENT_204
})
})
})
describe('When removing a video', function () {
it('Should have 404 with nothing', async function () {
await makeDeleteRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail without a correct uuid', async function () {
await server.videos.remove({ id: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with a video which does not exist', async function () {
await server.videos.remove({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should fail with a video of another user without the appropriate right', async function () {
await server.videos.remove({ token: userAccessToken, id: video.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail with a video of another server')
it('Shoud report the appropriate error', async function () {
const body = await server.videos.remove({ id: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
const error = body as PeerTubeProblemDocument
expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo')
expect(error.type).to.equal('about:blank')
expect(error.title).to.equal('Bad Request')
expect(error.detail).to.equal('Incorrect request parameters: id')
expect(error.error).to.equal('Incorrect request parameters: id')
expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
expect(error['invalid-params'].id).to.exist
})
it('Should succeed with the correct parameters', async function () {
await server.videos.remove({ id: video.uuid })
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+228
ファイルの表示
@@ -0,0 +1,228 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '@peertube/peertube-server-commands'
describe('Test videos views API validators', function () {
let servers: PeerTubeServer[]
let liveVideoId: string
let videoId: string
let remoteVideoId: string
let userAccessToken: string
before(async function () {
this.timeout(240000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await servers[0].config.enableLive({ allowReplay: false, transcoding: false });
({ uuid: videoId } = await servers[0].videos.quickUpload({ name: 'video' }));
({ uuid: remoteVideoId } = await servers[1].videos.quickUpload({ name: 'video' }));
({ uuid: liveVideoId } = await servers[0].live.create({
fields: {
name: 'live',
privacy: VideoPrivacy.PUBLIC,
channelId: servers[0].store.channel.id
}
}))
userAccessToken = await servers[0].users.generateUserAndToken('user')
await doubleFollow(servers[0], servers[1])
})
describe('When viewing a video', async function () {
it('Should fail without current time', async function () {
await servers[0].views.view({ id: videoId, currentTime: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an invalid current time', async function () {
await servers[0].views.view({ id: videoId, currentTime: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].views.view({ id: videoId, currentTime: -1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await servers[0].views.view({ id: videoId, currentTime: 10, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with correct parameters', async function () {
await servers[0].views.view({ id: videoId, currentTime: 1 })
})
})
describe('When getting overall stats', function () {
it('Should fail with a remote video', async function () {
await servers[0].videoStats.getOverallStats({ videoId: remoteVideoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should fail without token', async function () {
await servers[0].videoStats.getOverallStats({ videoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with another token', async function () {
await servers[0].videoStats.getOverallStats({
videoId,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid start date', async function () {
await servers[0].videoStats.getOverallStats({
videoId,
startDate: 'fake' as any,
endDate: new Date().toISOString(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an invalid end date', async function () {
await servers[0].videoStats.getOverallStats({
videoId,
startDate: new Date().toISOString(),
endDate: 'fake' as any,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await servers[0].videoStats.getOverallStats({
videoId,
startDate: new Date().toISOString(),
endDate: new Date().toISOString()
})
})
})
describe('When getting timeserie stats', function () {
it('Should fail with a remote video', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId: remoteVideoId,
metric: 'viewers',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail without token', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
token: null,
metric: 'viewers',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another token', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
token: userAccessToken,
metric: 'viewers',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid metric', async function () {
await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'hello' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should fail with an invalid start date', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
metric: 'viewers',
startDate: 'fake' as any,
endDate: new Date(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an invalid end date', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
metric: 'viewers',
startDate: new Date(),
endDate: 'fake' as any,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if start date is specified but not end date', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
metric: 'viewers',
startDate: new Date(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail if end date is specified but not start date', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
metric: 'viewers',
endDate: new Date(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with a too big interval', async function () {
await servers[0].videoStats.getTimeserieStats({
videoId,
metric: 'viewers',
startDate: new Date('2000-04-07T08:31:57.126Z'),
endDate: new Date(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed with the correct parameters', async function () {
await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'viewers' })
})
})
describe('When getting retention stats', function () {
it('Should fail with a remote video', async function () {
await servers[0].videoStats.getRetentionStats({
videoId: remoteVideoId,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail without token', async function () {
await servers[0].videoStats.getRetentionStats({
videoId,
token: null,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another token', async function () {
await servers[0].videoStats.getRetentionStats({
videoId,
token: userAccessToken,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail on live video', async function () {
await servers[0].videoStats.getRetentionStats({ videoId: liveVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with the correct parameters', async function () {
await servers[0].videoStats.getRetentionStats({ videoId })
})
})
after(async function () {
await cleanupTests(servers)
})
})
+254
ファイルの表示
@@ -0,0 +1,254 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
import {
PeerTubeServer,
WatchedWordsCommand,
cleanupTests,
createSingleServer,
makeGetRequest,
setAccessTokensToServers,
setDefaultAccountAvatar
} from '@peertube/peertube-server-commands'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
describe('Test watched words API validators', function () {
let server: PeerTubeServer
let userToken: string
let userToken2: string
let moderatorToken: string
let command: WatchedWordsCommand
let accountListId: number
let serverListId: number
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultAccountAvatar([ server ])
userToken = await server.users.generateUserAndToken('user1')
userToken2 = await server.users.generateUserAndToken('user2')
moderatorToken = await server.users.generateUserAndToken('moderator', UserRole.MODERATOR)
command = server.watchedWordsLists
{
const { watchedWordsList } = await command.createList({
accountName: 'user1',
token: userToken,
listName: 'default',
words: [ 'word1' ]
})
accountListId = watchedWordsList.id
}
{
const { watchedWordsList } = await command.createList({
listName: 'default',
words: [ 'word1' ]
})
serverListId = watchedWordsList.id
}
})
describe('Account & server watched words', function () {
describe('When listing watched words', function () {
const paths = [
'/api/v1/watched-words/accounts/user1/lists',
'/api/v1/watched-words/server/lists'
]
it('Should fail with an unauthenticated user', async function () {
for (const path of paths) {
await makeGetRequest({
url: server.url,
path,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
}
})
it('Should fail with the wrong token', async function () {
for (const path of paths) {
await makeGetRequest({
url: server.url,
token: userToken2,
path,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should fail with a bad start/count pagination or incorrect sort', async function () {
for (const path of paths) {
await checkBadStartPagination(server.url, path, userToken)
await checkBadCountPagination(server.url, path, userToken)
await checkBadSortPagination(server.url, path, userToken)
}
})
})
describe('When adding/updating watched words', function () {
const baseParams = () => ([
{
token: userToken,
accountName: 'user1',
listName: 'list',
words: [ 'word1' ],
listId: accountListId
},
{
token: moderatorToken,
listName: 'list',
words: [ 'word1' ],
listId: serverListId
}
])
it('Should fail with an unauthenticated user', async function () {
for (const baseParam of baseParams()) {
await command.createList({ ...baseParam, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await command.updateList({ ...baseParam, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}
})
it('Should fail with the wrong token', async function () {
for (const baseParam of baseParams()) {
await command.createList({ ...baseParam, token: userToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await command.updateList({ ...baseParam, token: userToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should fail with an invalid listName', async function () {
for (const baseParam of baseParams()) {
await command.createList({ ...baseParam, listName: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
for (const listName of [ '', 'a'.repeat(500) ]) {
await command.createList({ ...baseParam, listName, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.updateList({ ...baseParam, listName, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
}
})
it('Should fail with invalid words', async function () {
const bigArray: string[] = []
for (let i = 0; i < 550; i++) {
bigArray.push(`word${i}`)
}
const toTest = [
[],
bigArray,
[ 'a'.repeat(102) ],
[ '' ],
[ '', 'word' ]
]
for (const baseParam of baseParams()) {
await command.createList({ ...baseParam, words: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
for (const words of toTest) {
await command.createList({ ...baseParam, words, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.updateList({ ...baseParam, words, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
}
})
it('Should succeed with the correct params', async function () {
for (const baseParam of baseParams()) {
await command.createList(baseParam)
await command.updateList({ ...baseParam, listName: 'updated-list' })
}
})
it('Should succeed to update a list with the same name', async function () {
for (const baseParam of baseParams()) {
await command.updateList({ ...baseParam, listName: 'updated-list' })
}
})
it('Should fail to add a list with an already existing name', async function () {
for (const baseParam of baseParams()) {
await command.createList({ ...baseParam, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
await command.updateList({ ...baseParam, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}
})
})
describe('When deleting watched words', function () {
const baseParams = () => ([
{
token: userToken,
accountName: 'user1',
listId: accountListId
},
{
token: moderatorToken,
listId: serverListId
}
])
it('Should fail with an unauthenticated user', async function () {
for (const baseParam of baseParams()) {
await command.deleteList({ ...baseParam, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}
})
it('Should fail with the wrong token', async function () {
for (const baseParam of baseParams()) {
await command.deleteList({ ...baseParam, token: userToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
})
it('Should succeed with the correct params', async function () {
for (const baseParam of baseParams()) {
await command.deleteList(baseParam)
}
})
})
})
describe('Account specific watched words', function () {
describe('When listing watched words', function () {
it('Should fail with an unknown account', async function () {
await command.listWordsLists({ accountName: 'unknown', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('When adding/updating watched words', function () {
const baseParams = () => ({
token: userToken,
accountName: 'user1',
listName: 'list',
words: [ 'word1' ]
})
it('Should fail with an unknown account', async function () {
await command.createList({ ...baseParams(), accountName: 'unknown', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('When deleting watched words', function () {
const baseParams = () => ({
listId: accountListId,
token: userToken,
accountName: 'user1'
})
it('Should fail with an unknown account', async function () {
await command.deleteList({ ...baseParams(), accountName: 'unknown', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+8
ファイルの表示
@@ -0,0 +1,8 @@
import './live-constraints.js'
import './live-fast-restream.js'
import './live-socket-messages.js'
import './live-privacy-update.js'
import './live-permanent.js'
import './live-rtmps.js'
import './live-save-replay.js'
import './live.js'
+235
ファイルの表示
@@ -0,0 +1,235 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { LiveVideoError, UserVideoQuota, VideoPrivacy } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests, createMultipleServers,
doubleFollow,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs,
waitUntilLiveReplacedByReplayOnAllServers,
waitUntilLiveWaitingOnAllServers
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
import { checkLiveCleanup } from '../../shared/live.js'
describe('Test live constraints', function () {
let servers: PeerTubeServer[] = []
let userId: number
let userAccessToken: string
let userChannelId: number
async function createLiveWrapper (options: { replay: boolean, permanent: boolean }) {
const { replay, permanent } = options
const liveAttributes = {
name: 'user live',
channelId: userChannelId,
privacy: VideoPrivacy.PUBLIC,
saveReplay: replay,
replaySettings: options.replay ? { privacy: VideoPrivacy.PUBLIC } : undefined,
permanentLive: permanent
}
const { uuid } = await servers[0].live.create({ token: userAccessToken, fields: liveAttributes })
return uuid
}
async function checkSaveReplay (videoId: string, resolutions = [ 720 ]) {
for (const server of servers) {
const video = await server.videos.get({ id: videoId })
expect(video.isLive).to.be.false
expect(video.duration).to.be.greaterThan(0)
}
await checkLiveCleanup({ server: servers[0], permanent: false, videoUUID: videoId, savedResolutions: resolutions })
}
function updateQuota (options: { total: number, daily: number }) {
return servers[0].users.update({
userId,
videoQuota: options.total,
videoQuotaDaily: options.daily
})
}
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await servers[0].config.enableMinimumTranscoding()
await servers[0].config.enableLive({ allowReplay: true, transcoding: false })
{
const res = await servers[0].users.generate('user1')
userId = res.userId
userChannelId = res.userChannelId
userAccessToken = res.token
await updateQuota({ total: 1, daily: -1 })
}
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
})
it('Should not have size limit if save replay is disabled', async function () {
this.timeout(60000)
const userVideoLiveoId = await createLiveWrapper({ replay: false, permanent: false })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false })
})
it('Should have size limit depending on user global quota if save replay is enabled on non permanent live', async function () {
this.timeout(60000)
// Wait for user quota memoize cache invalidation
await wait(5000)
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
await waitJobs(servers)
await checkSaveReplay(userVideoLiveoId)
const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
})
it('Should have size limit depending on user global quota if save replay is enabled on a permanent live', async function () {
this.timeout(60000)
// Wait for user quota memoize cache invalidation
await wait(5000)
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: true })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
await waitJobs(servers)
await waitUntilLiveWaitingOnAllServers(servers, userVideoLiveoId)
const session = await servers[0].live.findLatestSession({ videoId: userVideoLiveoId })
expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
})
it('Should have size limit depending on user daily quota if save replay is enabled', async function () {
this.timeout(60000)
// Wait for user quota memoize cache invalidation
await wait(5000)
await updateQuota({ total: -1, daily: 1 })
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
await waitJobs(servers)
await checkSaveReplay(userVideoLiveoId)
const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
})
it('Should succeed without quota limit', async function () {
this.timeout(60000)
// Wait for user quota memoize cache invalidation
await wait(5000)
await updateQuota({ total: 10 * 1000 * 1000, daily: -1 })
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false })
})
it('Should have the same quota in admin and as a user', async function () {
this.timeout(120000)
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ token: userAccessToken, videoId: userVideoLiveoId })
await servers[0].live.waitUntilPublished({ videoId: userVideoLiveoId })
// Wait previous live cleanups
await wait(3000)
const baseQuota = await servers[0].users.getMyQuotaUsed({ token: userAccessToken })
let quotaUser: UserVideoQuota
do {
await wait(500)
quotaUser = await servers[0].users.getMyQuotaUsed({ token: userAccessToken })
} while (quotaUser.videoQuotaUsed <= baseQuota.videoQuotaUsed)
const { data } = await servers[0].users.list()
const quotaAdmin = data.find(u => u.username === 'user1')
expect(quotaUser.videoQuotaUsed).to.be.above(baseQuota.videoQuotaUsed)
expect(quotaUser.videoQuotaUsedDaily).to.be.above(baseQuota.videoQuotaUsedDaily)
expect(quotaAdmin.videoQuotaUsed).to.be.above(baseQuota.videoQuotaUsed)
expect(quotaAdmin.videoQuotaUsedDaily).to.be.above(baseQuota.videoQuotaUsedDaily)
expect(quotaUser.videoQuotaUsed).to.be.above(10)
expect(quotaUser.videoQuotaUsedDaily).to.be.above(10)
expect(quotaAdmin.videoQuotaUsed).to.be.above(10)
expect(quotaAdmin.videoQuotaUsedDaily).to.be.above(10)
await stopFfmpeg(ffmpegCommand)
})
it('Should have max duration limit', async function () {
this.timeout(240000)
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxDuration: 15,
transcoding: {
enabled: true,
resolutions: {
'144p': true,
'240p': true,
'360p': false,
'480p': false,
'720p': true,
'1080p': false,
'1440p': false,
'2160p': false
}
}
}
}
})
const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
await waitJobs(servers)
await checkSaveReplay(userVideoLiveoId, [ 720, 240, 144 ])
const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
expect(session.error).to.equal(LiveVideoError.DURATION_EXCEEDED)
})
after(async function () {
await cleanupTests(servers)
})
})
+153
ファイルの表示
@@ -0,0 +1,153 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { wait } from '@peertube/peertube-core-utils'
import { LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Fast restream in live', function () {
let server: PeerTubeServer
async function createLiveWrapper (options: { permanent: boolean, replay: boolean }) {
const attributes: LiveVideoCreate = {
channelId: server.store.channel.id,
privacy: VideoPrivacy.PUBLIC,
name: 'my super live',
saveReplay: options.replay,
replaySettings: options.replay ? { privacy: VideoPrivacy.PUBLIC } : undefined,
permanentLive: options.permanent
}
const { uuid } = await server.live.create({ fields: attributes })
return uuid
}
async function fastRestreamWrapper ({ replay }: { replay: boolean }) {
const liveVideoUUID = await createLiveWrapper({ permanent: true, replay })
await waitJobs([ server ])
const rtmpOptions = {
videoId: liveVideoUUID,
copyCodecs: true,
fixtureName: 'video_short.mp4'
}
// Streaming session #1
let ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
await server.live.waitUntilPublished({ videoId: liveVideoUUID })
const video = await server.videos.get({ id: liveVideoUUID })
const session1PlaylistId = video.streamingPlaylists[0].id
await stopFfmpeg(ffmpegCommand)
await server.live.waitUntilWaiting({ videoId: liveVideoUUID })
// Streaming session #2
ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
let hasNewPlaylist = false
do {
const video = await server.videos.get({ id: liveVideoUUID })
hasNewPlaylist = video.streamingPlaylists.length === 1 && video.streamingPlaylists[0].id !== session1PlaylistId
await wait(100)
} while (!hasNewPlaylist)
await server.live.waitUntilSegmentGeneration({
server,
videoUUID: liveVideoUUID,
segment: 1,
playlistNumber: 0
})
return { ffmpegCommand, liveVideoUUID }
}
async function ensureLastLiveWorks (liveId: string) {
// Equivalent to PEERTUBE_TEST_CONSTANTS_VIDEO_LIVE_CLEANUP_DELAY
for (let i = 0; i < 100; i++) {
const video = await server.videos.get({ id: liveId })
expect(video.streamingPlaylists).to.have.lengthOf(1)
try {
await server.live.getSegmentFile({ videoUUID: liveId, segment: 0, playlistNumber: 0 })
await server.streamingPlaylists.get({ url: video.streamingPlaylists[0].playlistUrl })
await server.streamingPlaylists.getSegmentSha256({ url: video.streamingPlaylists[0].segmentsSha256Url })
} catch (err) {
// FIXME: try to debug error in CI "Unexpected end of JSON input"
console.error(err)
throw err
}
await wait(100)
}
}
async function runTest (replay: boolean) {
const { ffmpegCommand, liveVideoUUID } = await fastRestreamWrapper({ replay })
// TODO: remove, we try to debug a test timeout failure here
console.log('Ensuring last live works')
await ensureLastLiveWorks(liveVideoUUID)
await stopFfmpeg(ffmpegCommand)
await server.live.waitUntilWaiting({ videoId: liveVideoUUID })
// Wait for replays
await waitJobs([ server ])
const { total, data: sessions } = await server.live.listSessions({ videoId: liveVideoUUID })
expect(total).to.equal(2)
expect(sessions).to.have.lengthOf(2)
for (const session of sessions) {
expect(session.error).to.be.null
if (replay) {
expect(session.replayVideo).to.exist
await server.videos.get({ id: session.replayVideo.uuid })
} else {
expect(session.replayVideo).to.not.exist
}
}
}
before(async function () {
this.timeout(120000)
const env = { PEERTUBE_TEST_CONSTANTS_VIDEO_LIVE_CLEANUP_DELAY: '10000' }
server = await createSingleServer(1, {}, { env })
// Get the access tokens
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableMinimumTranscoding({ webVideo: false, hls: true })
await server.config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
})
it('Should correctly fast restream in a permanent live with and without save replay', async function () {
this.timeout(480000)
// A test can take a long time, so prefer to run them in parallel
await Promise.all([
runTest(true),
runTest(false)
])
})
after(async function () {
await cleanupTests([ server ])
})
})
+205
ファイルの表示
@@ -0,0 +1,205 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { LiveVideoCreate, VideoPrivacy, VideoState, VideoStateType } from '@peertube/peertube-models'
import {
ConfigCommand,
PeerTubeServer,
cleanupTests,
createMultipleServers,
doubleFollow,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkLiveCleanup } from '@tests/shared/live.js'
import { expect } from 'chai'
describe('Permanent live', function () {
let servers: PeerTubeServer[] = []
let videoUUID: string
async function createLiveWrapper (permanentLive: boolean) {
const attributes: LiveVideoCreate = {
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
name: 'my super live',
saveReplay: false,
permanentLive
}
const { uuid } = await servers[0].live.create({ fields: attributes })
return uuid
}
async function checkVideoState (videoId: string, state: VideoStateType) {
for (const server of servers) {
const video = await server.videos.get({ id: videoId })
expect(video.state.id).to.equal(state)
}
}
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableMinimumTranscoding()
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxDuration: -1,
transcoding: {
enabled: true,
resolutions: ConfigCommand.getCustomConfigResolutions(true)
}
}
}
})
})
it('Should create a non permanent live and update it to be a permanent live', async function () {
this.timeout(20000)
const videoUUID = await createLiveWrapper(false)
{
const live = await servers[0].live.get({ videoId: videoUUID })
expect(live.permanentLive).to.be.false
}
await servers[0].live.update({ videoId: videoUUID, fields: { permanentLive: true } })
{
const live = await servers[0].live.get({ videoId: videoUUID })
expect(live.permanentLive).to.be.true
}
})
it('Should create a permanent live', async function () {
this.timeout(20000)
videoUUID = await createLiveWrapper(true)
const live = await servers[0].live.get({ videoId: videoUUID })
expect(live.permanentLive).to.be.true
await waitJobs(servers)
})
it('Should stream into this permanent live', async function () {
this.timeout(240_000)
const beforePublication = new Date()
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
for (const server of servers) {
await server.live.waitUntilPublished({ videoId: videoUUID })
}
await checkVideoState(videoUUID, VideoState.PUBLISHED)
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(new Date(video.publishedAt)).greaterThan(beforePublication)
}
await stopFfmpeg(ffmpegCommand)
await servers[0].live.waitUntilWaiting({ videoId: videoUUID })
await waitJobs(servers)
})
it('Should have cleaned up this live', async function () {
this.timeout(40000)
await wait(5000)
await waitJobs(servers)
for (const server of servers) {
const videoDetails = await server.videos.get({ id: videoUUID })
expect(videoDetails.streamingPlaylists).to.have.lengthOf(0)
}
await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
})
it('Should have set this live to waiting for live state', async function () {
this.timeout(20000)
await checkVideoState(videoUUID, VideoState.WAITING_FOR_LIVE)
})
it('Should be able to stream again in the permanent live', async function () {
this.timeout(60000)
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxDuration: -1,
transcoding: {
enabled: true,
resolutions: ConfigCommand.getCustomConfigResolutions(false)
}
}
}
})
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
for (const server of servers) {
await server.live.waitUntilPublished({ videoId: videoUUID })
}
await checkVideoState(videoUUID, VideoState.PUBLISHED)
const count = await servers[0].live.countPlaylists({ videoUUID })
// master playlist and 720p playlist
expect(count).to.equal(2)
await stopFfmpeg(ffmpegCommand)
})
it('Should have appropriate sessions', async function () {
this.timeout(60000)
await servers[0].live.waitUntilWaiting({ videoId: videoUUID })
const { data, total } = await servers[0].live.listSessions({ videoId: videoUUID })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
for (const session of data) {
expect(session.startDate).to.exist
expect(session.endDate).to.exist
expect(session.error).to.not.exist
}
})
it('Should remove the live and have cleaned up the directory', async function () {
this.timeout(60000)
await servers[0].videos.remove({ id: videoUUID })
await waitJobs(servers)
await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
})
after(async function () {
await cleanupTests(servers)
})
})
+83
ファイルの表示
@@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests, createSingleServer, makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs,
waitUntilLivePublishedOnAllServers,
waitUntilLiveReplacedByReplayOnAllServers
} from '@peertube/peertube-server-commands'
async function testVideoFiles (server: PeerTubeServer, uuid: string) {
const video = await server.videos.getWithToken({ id: uuid })
const expectedStatus = HttpStatusCode.OK_200
await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, token: server.accessToken, expectedStatus })
await makeRawRequest({ url: video.streamingPlaylists[0].segmentsSha256Url, token: server.accessToken, expectedStatus })
}
describe('Live privacy update', function () {
let server: PeerTubeServer
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableMinimumTranscoding()
await server.config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
})
describe('Normal live', function () {
let uuid: string
it('Should create a public live with private replay', async function () {
this.timeout(120000)
const fields: LiveVideoCreate = {
name: 'live',
privacy: VideoPrivacy.PUBLIC,
permanentLive: false,
replaySettings: { privacy: VideoPrivacy.PRIVATE },
saveReplay: true,
channelId: server.store.channel.id
}
const video = await server.live.create({ fields })
uuid = video.uuid
const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: uuid })
await waitUntilLivePublishedOnAllServers([ server ], uuid)
await stopFfmpeg(ffmpegCommand)
await waitUntilLiveReplacedByReplayOnAllServers([ server ], uuid)
await waitJobs([ server ])
await testVideoFiles(server, uuid)
})
it('Should update the replay to public and re-update it to private', async function () {
this.timeout(120000)
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
await waitJobs([ server ])
await testVideoFiles(server, uuid)
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PRIVATE } })
await waitJobs([ server ])
await testVideoFiles(server, uuid)
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+134
ファイルの表示
@@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { VideoPrivacy } from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
sendRTMPStream,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
testFfmpegStreamError,
waitUntilLivePublishedOnAllServers
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test live RTMPS', function () {
let server: PeerTubeServer
let rtmpUrl: string
let rtmpsUrl: string
async function createLiveWrapper () {
const liveAttributes = {
name: 'live',
channelId: server.store.channel.id,
privacy: VideoPrivacy.PUBLIC,
saveReplay: false
}
const { uuid } = await server.live.create({ fields: liveAttributes })
const live = await server.live.get({ videoId: uuid })
const video = await server.videos.get({ id: uuid })
return Object.assign(video, live)
}
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
// Get the access tokens
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableMinimumTranscoding()
await server.config.enableLive({ allowReplay: true, transcoding: false })
rtmpUrl = 'rtmp://' + server.hostname + ':' + server.rtmpPort + '/live'
rtmpsUrl = 'rtmps://' + server.hostname + ':' + server.rtmpsPort + '/live'
})
it('Should enable RTMPS endpoint only', async function () {
this.timeout(240000)
await server.kill()
await server.run({
live: {
rtmp: {
enabled: false
},
rtmps: {
enabled: true,
port: server.rtmpsPort,
key_file: buildAbsoluteFixturePath('rtmps.key'),
cert_file: buildAbsoluteFixturePath('rtmps.cert')
}
}
})
{
const liveVideo = await createLiveWrapper()
expect(liveVideo.rtmpUrl).to.not.exist
expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, true)
}
{
const liveVideo = await createLiveWrapper()
const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
await stopFfmpeg(command)
}
})
it('Should enable both RTMP and RTMPS', async function () {
this.timeout(240000)
await server.kill()
await server.run({
live: {
rtmp: {
enabled: true,
port: server.rtmpPort
},
rtmps: {
enabled: true,
port: server.rtmpsPort,
key_file: buildAbsoluteFixturePath('rtmps.key'),
cert_file: buildAbsoluteFixturePath('rtmps.cert')
}
}
})
{
const liveVideo = await createLiveWrapper()
expect(liveVideo.rtmpUrl).to.equal(rtmpUrl)
expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
await stopFfmpeg(command)
}
{
const liveVideo = await createLiveWrapper()
const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
await stopFfmpeg(command)
}
})
after(async function () {
await cleanupTests([ server ])
})
})
+606
ファイルの表示
@@ -0,0 +1,606 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
HttpStatusCodeType,
LiveVideoCreate,
LiveVideoError,
VideoPrivacy,
VideoPrivacyType,
VideoState,
VideoStateType
} from '@peertube/peertube-models'
import {
ConfigCommand,
PeerTubeServer,
cleanupTests,
createMultipleServers,
doubleFollow,
findExternalSavedVideo,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
testFfmpegStreamError,
waitJobs,
waitUntilLivePublishedOnAllServers,
waitUntilLiveReplacedByReplayOnAllServers,
waitUntilLiveWaitingOnAllServers
} from '@peertube/peertube-server-commands'
import { checkLiveCleanup } from '@tests/shared/live.js'
import { expect } from 'chai'
import { FfmpegCommand } from 'fluent-ffmpeg'
describe('Save replay setting', function () {
let servers: PeerTubeServer[] = []
let liveVideoUUID: string
let ffmpegCommand: FfmpegCommand
async function createLiveWrapper (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacyType } }) {
if (liveVideoUUID) {
try {
await servers[0].videos.remove({ id: liveVideoUUID })
await waitJobs(servers)
} catch {}
}
const attributes: LiveVideoCreate = {
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
name: 'live'.repeat(30),
saveReplay: options.replay,
replaySettings: options.replaySettings,
permanentLive: options.permanent
}
const { uuid } = await servers[0].live.create({ fields: attributes })
return uuid
}
async function publishLive (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacyType } }) {
liveVideoUUID = await createLiveWrapper(options)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, null, HttpStatusCode.OK_200)
return { ffmpegCommand, liveDetails }
}
async function publishLiveAndDelete (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacyType } }) {
const { ffmpegCommand, liveDetails } = await publishLive(options)
await Promise.all([
servers[0].videos.remove({ id: liveVideoUUID }),
testFfmpegStreamError(ffmpegCommand, true)
])
await waitJobs(servers)
await wait(5000)
await waitJobs(servers)
return { liveDetails }
}
async function publishLiveAndBlacklist (options: {
permanent: boolean
replay: boolean
replaySettings?: { privacy: VideoPrivacyType }
}) {
const { ffmpegCommand, liveDetails } = await publishLive(options)
await Promise.all([
servers[0].blacklist.add({ videoId: liveVideoUUID, reason: 'bad live', unfederate: true }),
testFfmpegStreamError(ffmpegCommand, true)
])
await waitJobs(servers)
await wait(5000)
await waitJobs(servers)
return { liveDetails }
}
async function checkVideosExist (videoId: string, videosLength: number, expectedStatus?: HttpStatusCodeType) {
for (const server of servers) {
const { data, total } = await server.videos.list()
if (videosLength !== null) {
expect(data).to.have.lengthOf(videosLength)
expect(total).to.equal(videosLength)
}
if (expectedStatus) {
await server.videos.get({ id: videoId, expectedStatus })
}
}
}
async function checkVideoState (videoId: string, state: VideoStateType) {
for (const server of servers) {
const video = await server.videos.get({ id: videoId })
expect(video.state.id).to.equal(state)
}
}
async function checkVideoPrivacy (videoId: string, privacy: VideoPrivacyType) {
for (const server of servers) {
const video = await server.videos.get({ id: videoId })
expect(video.privacy.id).to.equal(privacy)
}
}
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableMinimumTranscoding()
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxDuration: -1,
transcoding: {
enabled: false,
resolutions: ConfigCommand.getCustomConfigResolutions(true)
}
}
}
})
})
describe('With save replay disabled', function () {
let sessionStartDateMin: Date
let sessionStartDateMax: Date
let sessionEndDateMin: Date
it('Should correctly create and federate the "waiting for stream" live', async function () {
this.timeout(40000)
liveVideoUUID = await createLiveWrapper({ permanent: false, replay: false })
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
})
it('Should correctly have updated the live and federated it when streaming in the live', async function () {
this.timeout(120000)
ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
sessionStartDateMin = new Date()
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
sessionStartDateMax = new Date()
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 1, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
})
it('Should correctly delete the video files after the stream ended', async function () {
this.timeout(120000)
sessionEndDateMin = new Date()
await stopFfmpeg(ffmpegCommand)
for (const server of servers) {
await server.live.waitUntilEnded({ videoId: liveVideoUUID })
}
await waitJobs(servers)
// Live still exist, but cannot be played anymore
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
// No resolutions saved since we did not save replay
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
it('Should have appropriate ended session', async function () {
const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
const session = data[0]
const startDate = new Date(session.startDate)
expect(startDate).to.be.above(sessionStartDateMin)
expect(startDate).to.be.below(sessionStartDateMax)
expect(session.endDate).to.exist
expect(new Date(session.endDate)).to.be.above(sessionEndDateMin)
expect(session.saveReplay).to.be.false
expect(session.error).to.not.exist
expect(session.replayVideo).to.not.exist
})
it('Should correctly terminate the stream on blacklist and delete the live', async function () {
this.timeout(120000)
await publishLiveAndBlacklist({ permanent: false, replay: false })
await checkVideosExist(liveVideoUUID, 0)
await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await wait(5000)
await waitJobs(servers)
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
it('Should have blacklisted session error', async function () {
const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
expect(session.startDate).to.exist
expect(session.endDate).to.exist
expect(session.error).to.equal(LiveVideoError.BLACKLISTED)
expect(session.replayVideo).to.not.exist
})
it('Should correctly terminate the stream on delete and delete the video', async function () {
this.timeout(120000)
await publishLiveAndDelete({ permanent: false, replay: false })
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.NOT_FOUND_404)
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
})
describe('With save replay enabled on non permanent live', function () {
it('Should correctly create and federate the "waiting for stream" live', async function () {
this.timeout(120000)
liveVideoUUID = await createLiveWrapper({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
})
it('Should correctly have updated the live and federated it when streaming in the live', async function () {
this.timeout(120000)
ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 1, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
})
it('Should correctly have saved the live and federated it after the streaming', async function () {
this.timeout(120000)
const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
expect(session.endDate).to.not.exist
expect(session.endingProcessed).to.be.false
expect(session.saveReplay).to.be.true
expect(session.replaySettings).to.exist
expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
await stopFfmpeg(ffmpegCommand)
await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
// Live has been transcoded
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.UNLISTED)
})
it('Should find the replay live session', async function () {
const session = await servers[0].live.getReplaySession({ videoId: liveVideoUUID })
expect(session).to.exist
expect(session.startDate).to.exist
expect(session.endDate).to.exist
expect(session.error).to.not.exist
expect(session.saveReplay).to.be.true
expect(session.endingProcessed).to.be.true
expect(session.replaySettings).to.exist
expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
expect(session.replayVideo).to.exist
expect(session.replayVideo.id).to.exist
expect(session.replayVideo.shortUUID).to.exist
expect(session.replayVideo.uuid).to.equal(liveVideoUUID)
})
it('Should update the saved live and correctly federate the updated attributes', async function () {
this.timeout(120000)
await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'video updated', privacy: VideoPrivacy.PUBLIC } })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: liveVideoUUID })
expect(video.name).to.equal('video updated')
expect(video.isLive).to.be.false
expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC)
}
})
it('Should have cleaned up the live files', async function () {
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
})
it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
this.timeout(120000)
await publishLiveAndBlacklist({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
await checkVideosExist(liveVideoUUID, 0)
await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await wait(5000)
await waitJobs(servers)
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
})
it('Should correctly terminate the stream on delete and delete the video', async function () {
this.timeout(120000)
await publishLiveAndDelete({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.NOT_FOUND_404)
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
})
describe('With save replay enabled on permanent live', function () {
let lastReplayUUID: string
describe('With a first live and its replay', function () {
before(async function () {
this.timeout(120000)
await servers[0].kill()
await servers[0].run({
federation: {
videos: {
federate_unlisted: false
}
}
})
})
it('Should correctly create and federate the "waiting for stream" live', async function () {
this.timeout(120000)
liveVideoUUID = await createLiveWrapper({ permanent: true, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 0, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
})
it('Should correctly have updated the live and federated it when streaming in the live', async function () {
this.timeout(120000)
ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 1, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
})
it('Should correctly have saved the live', async function () {
this.timeout(120000)
const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
await stopFfmpeg(ffmpegCommand)
await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
const video = await findExternalSavedVideo(servers[0], liveDetails)
expect(video).to.exist
await servers[0].videos.get({ id: video.uuid })
await servers[1].videos.get({ id: video.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
lastReplayUUID = video.uuid
})
it('Should federate the replay after updating its privacy to public', async function () {
this.timeout(120000)
await servers[0].videos.update({ id: lastReplayUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
await waitJobs(servers)
await servers[1].videos.get({ id: lastReplayUUID, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should have appropriate ended session and replay live session', async function () {
const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
const sessionFromLive = data[0]
const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
for (const session of [ sessionFromLive, sessionFromReplay ]) {
expect(session.startDate).to.exist
expect(session.endDate).to.exist
expect(session.replaySettings).to.exist
expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
expect(session.error).to.not.exist
expect(session.replayVideo).to.exist
expect(session.replayVideo.id).to.exist
expect(session.replayVideo.shortUUID).to.exist
expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
}
})
it('Should have the first live replay with correct settings', async function () {
await checkVideosExist(lastReplayUUID, 1, HttpStatusCode.OK_200)
await checkVideoState(lastReplayUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(lastReplayUUID, VideoPrivacy.PUBLIC)
})
})
describe('With a second live and its replay', function () {
it('Should update the replay settings', async function () {
await servers[0].live.update({ videoId: liveVideoUUID, fields: { replaySettings: { privacy: VideoPrivacy.PUBLIC } } })
await waitJobs(servers)
const live = await servers[0].live.get({ videoId: liveVideoUUID })
expect(live.saveReplay).to.be.true
expect(live.replaySettings).to.exist
expect(live.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
})
it('Should correctly have updated the live and federated it when streaming in the live', async function () {
this.timeout(120000)
ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
await checkVideosExist(liveVideoUUID, 2, HttpStatusCode.OK_200)
await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
})
it('Should correctly have saved the live and federated it after the streaming', async function () {
this.timeout(120000)
const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
await stopFfmpeg(ffmpegCommand)
await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
const video = await findExternalSavedVideo(servers[0], liveDetails)
expect(video).to.exist
for (const server of servers) {
await server.videos.get({ id: video.uuid })
}
lastReplayUUID = video.uuid
})
it('Should have appropriate ended session and replay live session', async function () {
const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
const sessionFromLive = data[1]
const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
for (const session of [ sessionFromLive, sessionFromReplay ]) {
expect(session.startDate).to.exist
expect(session.endDate).to.exist
expect(session.replaySettings).to.exist
expect(session.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
expect(session.error).to.not.exist
expect(session.replayVideo).to.exist
expect(session.replayVideo.id).to.exist
expect(session.replayVideo.shortUUID).to.exist
expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
}
})
it('Should have the first live replay with correct settings', async function () {
await checkVideosExist(lastReplayUUID, 2, HttpStatusCode.OK_200)
await checkVideoState(lastReplayUUID, VideoState.PUBLISHED)
await checkVideoPrivacy(lastReplayUUID, VideoPrivacy.PUBLIC)
})
it('Should have cleaned up the live files', async function () {
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
this.timeout(120000)
await servers[0].videos.remove({ id: lastReplayUUID })
const { liveDetails } = await publishLiveAndBlacklist({
permanent: true,
replay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC }
})
const replay = await findExternalSavedVideo(servers[0], liveDetails)
expect(replay).to.exist
for (const videoId of [ liveVideoUUID, replay.uuid ]) {
await checkVideosExist(videoId, 1)
await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
it('Should correctly terminate the stream on delete and not save the video', async function () {
this.timeout(120000)
const { liveDetails } = await publishLiveAndDelete({
permanent: true,
replay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC }
})
const replay = await findExternalSavedVideo(servers[0], liveDetails)
expect(replay).to.not.exist
await checkVideosExist(liveVideoUUID, 1, HttpStatusCode.NOT_FOUND_404)
await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
})
})
})
after(async function () {
await cleanupTests(servers)
})
})
+181
ファイルの表示
@@ -0,0 +1,181 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { LiveVideoEventPayload, VideoPrivacy, VideoState, VideoStateType } from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createMultipleServers,
doubleFollow,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs,
waitUntilLivePublishedOnAllServers
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test live socket messages', function () {
let servers: PeerTubeServer[] = []
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await servers[0].config.enableMinimumTranscoding()
await servers[0].config.enableLive({ allowReplay: true, transcoding: false })
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
})
describe('Live socket messages', function () {
async function createLiveWrapper () {
const liveAttributes = {
name: 'live video',
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC
}
const { uuid } = await servers[0].live.create({ fields: liveAttributes })
return uuid
}
it('Should correctly send a message when the live starts and ends', async function () {
this.timeout(60000)
const localStateChanges: VideoStateType[] = []
const remoteStateChanges: VideoStateType[] = []
const liveVideoUUID = await createLiveWrapper()
await waitJobs(servers)
{
const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
const localSocket = servers[0].socketIO.getLiveNotificationSocket()
localSocket.on('state-change', data => localStateChanges.push(data.state))
localSocket.emit('subscribe', { videoId })
}
{
const videoId = await servers[1].videos.getId({ uuid: liveVideoUUID })
const remoteSocket = servers[1].socketIO.getLiveNotificationSocket()
remoteSocket.on('state-change', data => remoteStateChanges.push(data.state))
remoteSocket.emit('subscribe', { videoId })
}
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
// Ensure remote server doesn't send multiple times the state change event to viewers
await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'my new live name' } })
await waitJobs(servers)
for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
expect(stateChanges).to.have.lengthOf(1)
expect(stateChanges[0]).to.equal(VideoState.PUBLISHED)
}
await stopFfmpeg(ffmpegCommand)
for (const server of servers) {
await server.live.waitUntilEnded({ videoId: liveVideoUUID })
}
await waitJobs(servers)
for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
expect(stateChanges).to.have.length.at.least(2)
expect(stateChanges[stateChanges.length - 1]).to.equal(VideoState.LIVE_ENDED)
}
})
it('Should correctly send views change notification', async function () {
this.timeout(60000)
let localLastVideoViews = 0
let remoteLastVideoViews = 0
const liveVideoUUID = await createLiveWrapper()
await waitJobs(servers)
{
const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
const localSocket = servers[0].socketIO.getLiveNotificationSocket()
localSocket.on('views-change', (data: LiveVideoEventPayload) => { localLastVideoViews = data.viewers })
localSocket.emit('subscribe', { videoId })
}
{
const videoId = await servers[1].videos.getId({ uuid: liveVideoUUID })
const remoteSocket = servers[1].socketIO.getLiveNotificationSocket()
remoteSocket.on('views-change', (data: LiveVideoEventPayload) => { remoteLastVideoViews = data.viewers })
remoteSocket.emit('subscribe', { videoId })
}
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
expect(localLastVideoViews).to.equal(0)
expect(remoteLastVideoViews).to.equal(0)
await servers[0].views.simulateView({ id: liveVideoUUID })
await servers[1].views.simulateView({ id: liveVideoUUID })
await waitJobs(servers)
expect(localLastVideoViews).to.equal(2)
expect(remoteLastVideoViews).to.equal(2)
await stopFfmpeg(ffmpegCommand)
})
it('Should not receive a notification after unsubscribe', async function () {
this.timeout(120000)
const stateChanges: VideoStateType[] = []
const liveVideoUUID = await createLiveWrapper()
await waitJobs(servers)
const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
const socket = servers[0].socketIO.getLiveNotificationSocket()
socket.on('state-change', data => stateChanges.push(data.state))
socket.emit('subscribe', { videoId })
const command = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
await waitJobs(servers)
// Notifier waits before sending a notification
await wait(10000)
expect(stateChanges).to.have.lengthOf(1)
socket.emit('unsubscribe', { videoId })
await stopFfmpeg(command)
await waitJobs(servers)
expect(stateChanges).to.have.lengthOf(1)
})
})
after(async function () {
await cleanupTests(servers)
})
})
+782
ファイルの表示
@@ -0,0 +1,782 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { basename, join } from 'path'
import { getAllFiles, wait } from '@peertube/peertube-core-utils'
import { ffprobePromise, getVideoStream } from '@peertube/peertube-ffmpeg'
import {
HttpStatusCode,
LiveVideo,
LiveVideoCreate,
LiveVideoLatencyMode,
VideoCommentPolicy,
VideoDetails,
VideoPrivacy,
VideoState,
VideoStreamingPlaylistType
} from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
killallServers,
LiveCommand,
makeGetRequest,
makeRawRequest,
PeerTubeServer,
sendRTMPStream,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
testFfmpegStreamError,
waitJobs,
waitUntilLivePublishedOnAllServers
} from '@peertube/peertube-server-commands'
import { testImageGeneratedByFFmpeg } from '@tests/shared/checks.js'
import { testLiveVideoResolutions } from '@tests/shared/live.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
describe('Test live', function () {
let servers: PeerTubeServer[] = []
let commands: LiveCommand[]
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await servers[0].config.enableMinimumTranscoding()
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxUserLives: -1,
latencySetting: {
enabled: true
},
transcoding: {
enabled: false
}
}
}
})
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
commands = servers.map(s => s.live)
})
describe('Live creation, update and delete', function () {
let liveVideoUUID: string
it('Should create a live with the appropriate parameters', async function () {
this.timeout(20000)
const attributes: LiveVideoCreate = {
category: 1,
licence: 2,
language: 'fr',
description: 'super live description',
support: 'support field',
channelId: servers[0].store.channel.id,
nsfw: false,
waitTranscoding: false,
name: 'my super live',
tags: [ 'tag1', 'tag2' ],
commentsPolicy: VideoCommentPolicy.DISABLED,
downloadEnabled: false,
saveReplay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC },
latencyMode: LiveVideoLatencyMode.SMALL_LATENCY,
privacy: VideoPrivacy.PUBLIC,
previewfile: 'video_short1-preview.webm.jpg',
thumbnailfile: 'video_short1.webm.jpg'
}
const live = await commands[0].create({ fields: attributes })
liveVideoUUID = live.uuid
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: liveVideoUUID })
expect(video.category.id).to.equal(1)
expect(video.licence.id).to.equal(2)
expect(video.language.id).to.equal('fr')
expect(video.description).to.equal('super live description')
expect(video.support).to.equal('support field')
expect(video.channel.name).to.equal(servers[0].store.channel.name)
expect(video.channel.host).to.equal(servers[0].store.channel.host)
expect(video.isLive).to.be.true
expect(video.aspectRatio).to.not.exist
expect(video.nsfw).to.be.false
expect(video.waitTranscoding).to.be.false
expect(video.name).to.equal('my super live')
expect(video.tags).to.deep.equal([ 'tag1', 'tag2' ])
expect(video.commentsEnabled).to.be.false
expect(video.downloadEnabled).to.be.false
expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC)
await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath)
await testImageGeneratedByFFmpeg(server.url, 'video_short1.webm', video.thumbnailPath)
const live = await server.live.get({ videoId: liveVideoUUID })
if (server.url === servers[0].url) {
expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
expect(live.streamKey).to.not.be.empty
expect(live.replaySettings).to.exist
expect(live.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
} else {
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
}
expect(live.saveReplay).to.be.true
expect(live.latencyMode).to.equal(LiveVideoLatencyMode.SMALL_LATENCY)
}
})
it('Should have a default preview and thumbnail', async function () {
this.timeout(20000)
const attributes: LiveVideoCreate = {
name: 'default live thumbnail',
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.UNLISTED,
nsfw: true
}
const live = await commands[0].create({ fields: attributes })
const videoId = live.uuid
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: videoId })
expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED)
expect(video.nsfw).to.be.true
await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should not have the live listed since nobody streams into', async function () {
for (const server of servers) {
const { total, data } = await server.videos.list()
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
}
})
it('Should not be able to update a live of another server', async function () {
await commands[1].update({ videoId: liveVideoUUID, fields: { saveReplay: false }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
})
it('Should update the live', async function () {
await commands[0].update({ videoId: liveVideoUUID, fields: { saveReplay: false, latencyMode: LiveVideoLatencyMode.DEFAULT } })
await waitJobs(servers)
})
it('Have the live updated', async function () {
for (const server of servers) {
const live = await server.live.get({ videoId: liveVideoUUID })
if (server.url === servers[0].url) {
expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
expect(live.streamKey).to.not.be.empty
} else {
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
}
expect(live.saveReplay).to.be.false
expect(live.replaySettings).to.not.exist
expect(live.latencyMode).to.equal(LiveVideoLatencyMode.DEFAULT)
}
})
it('Delete the live', async function () {
await servers[0].videos.remove({ id: liveVideoUUID })
await waitJobs(servers)
})
it('Should have the live deleted', async function () {
for (const server of servers) {
await server.videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await server.live.get({ videoId: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
})
})
describe('Live filters', function () {
let ffmpegCommand: any
let liveVideoId: string
let vodVideoId: string
before(async function () {
this.timeout(240000)
vodVideoId = (await servers[0].videos.quickUpload({ name: 'vod video' })).uuid
const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].store.channel.id }
const live = await commands[0].create({ fields: liveOptions })
liveVideoId = live.uuid
ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoId })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
})
it('Should only display lives', async function () {
const { data, total } = await servers[0].videos.list({ isLive: true })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].name).to.equal('live')
})
it('Should not display lives', async function () {
const { data, total } = await servers[0].videos.list({ isLive: false })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].name).to.equal('vod video')
})
it('Should display my lives', async function () {
this.timeout(60000)
await stopFfmpeg(ffmpegCommand)
await waitJobs(servers)
const { data } = await servers[0].videos.listMyVideos({ isLive: true })
const result = data.every(v => v.isLive)
expect(result).to.be.true
})
it('Should not display my lives', async function () {
const { data } = await servers[0].videos.listMyVideos({ isLive: false })
const result = data.every(v => !v.isLive)
expect(result).to.be.true
})
after(async function () {
await servers[0].videos.remove({ id: vodVideoId })
await servers[0].videos.remove({ id: liveVideoId })
})
})
describe('Stream checks', function () {
let liveVideo: LiveVideo & VideoDetails
let rtmpUrl: string
before(function () {
rtmpUrl = 'rtmp://' + servers[0].hostname + ':' + servers[0].rtmpPort + ''
})
async function createLiveWrapper (token?: string, channelId?: number) {
const { uuid } = await commands[0].create({
token,
fields: {
name: 'user live',
channelId: channelId ?? servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
saveReplay: false
}
})
const live = await commands[0].get({ videoId: uuid })
const video = await servers[0].videos.get({ id: uuid })
return Object.assign(video, live)
}
it('Should not allow a stream without the appropriate path', async function () {
this.timeout(60000)
liveVideo = await createLiveWrapper()
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/bad-live', streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, true)
})
it('Should not allow a stream without the appropriate stream key', async function () {
this.timeout(60000)
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: 'bad-stream-key' })
await testFfmpegStreamError(command, true)
})
it('Should succeed with the correct params', async function () {
this.timeout(60000)
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, false)
})
it('Should list this live now someone stream into it', async function () {
for (const server of servers) {
const { total, data } = await server.videos.list()
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
const video = data[0]
expect(video.name).to.equal('user live')
expect(video.isLive).to.be.true
}
})
it('Should not allow a stream on a live that was blacklisted', async function () {
this.timeout(60000)
liveVideo = await createLiveWrapper()
await servers[0].blacklist.add({ videoId: liveVideo.uuid })
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, true)
})
it('Should not allow a stream on if the owner has been blocked', async function () {
this.timeout(60000)
const { token, userId, userChannelId } = await servers[0].users.generate('user1')
liveVideo = await createLiveWrapper(token, userChannelId)
await servers[0].users.banUser({ userId })
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, true)
})
it('Should not allow a stream on a live that was deleted', async function () {
this.timeout(60000)
liveVideo = await createLiveWrapper()
await servers[0].videos.remove({ id: liveVideo.uuid })
const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
await testFfmpegStreamError(command, true)
})
})
describe('Live transcoding', function () {
let liveVideoId: string
let sqlCommandServer1: SQLCommand
async function createLiveWrapper (saveReplay: boolean) {
const liveAttributes = {
name: 'live video',
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
saveReplay,
replaySettings: saveReplay
? { privacy: VideoPrivacy.PUBLIC }
: undefined
}
const { uuid } = await commands[0].create({ fields: liveAttributes })
return uuid
}
function updateConf (resolutions: number[]) {
return servers[0].config.updateExistingConfig({
newConfig: {
live: {
enabled: true,
allowReplay: true,
maxDuration: -1,
transcoding: {
enabled: true,
resolutions: {
'144p': resolutions.includes(144),
'240p': resolutions.includes(240),
'360p': resolutions.includes(360),
'480p': resolutions.includes(480),
'720p': resolutions.includes(720),
'1080p': resolutions.includes(1080),
'2160p': resolutions.includes(2160)
}
}
}
}
})
}
before(async function () {
await updateConf([])
sqlCommandServer1 = new SQLCommand(servers[0])
})
it('Should enable transcoding without additional resolutions', async function () {
this.timeout(120000)
liveVideoId = await createLiveWrapper(false)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId,
resolutions: [ 720 ],
transcoded: true
})
await stopFfmpeg(ffmpegCommand)
})
it('Should transcode audio only RTMP stream', async function () {
this.timeout(120000)
liveVideoId = await createLiveWrapper(false)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short_no_audio.mp4' })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await stopFfmpeg(ffmpegCommand)
})
it('Should enable transcoding with some resolutions', async function () {
this.timeout(240000)
const resolutions = [ 240, 480 ]
await updateConf(resolutions)
liveVideoId = await createLiveWrapper(false)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId,
resolutions: resolutions.concat([ 720 ]),
transcoded: true
})
await stopFfmpeg(ffmpegCommand)
})
it('Should correctly set the appropriate bitrate depending on the input', async function () {
this.timeout(120000)
liveVideoId = await createLiveWrapper(false)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({
videoId: liveVideoId,
fixtureName: 'video_short.mp4',
copyCodecs: true
})
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
const video = await servers[0].videos.get({ id: liveVideoId })
const masterPlaylist = video.streamingPlaylists[0].playlistUrl
const probe = await ffprobePromise(masterPlaylist)
const bitrates = probe.streams.map(s => parseInt(s.tags.variant_bitrate))
for (const bitrate of bitrates) {
expect(bitrate).to.exist
expect(isNaN(bitrate)).to.be.false
expect(bitrate).to.be.below(61_000_000) // video_short.mp4 bitrate
}
await stopFfmpeg(ffmpegCommand)
})
it('Should enable transcoding with some resolutions and correctly save them', async function () {
this.timeout(500_000)
const resolutions = [ 240, 360, 720 ]
await updateConf(resolutions)
liveVideoId = await createLiveWrapper(true)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId,
resolutions,
transcoded: true
})
await stopFfmpeg(ffmpegCommand)
await commands[0].waitUntilEnded({ videoId: liveVideoId })
await waitJobs(servers)
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
const maxBitrateLimits = {
720: 6500 * 1000, // 60FPS
360: 1250 * 1000,
240: 700 * 1000
}
const minBitrateLimits = {
720: 4800 * 1000,
360: 1000 * 1000,
240: 550 * 1000
}
for (const server of servers) {
const video = await server.videos.get({ id: liveVideoId })
expect(video.state.id).to.equal(VideoState.PUBLISHED)
expect(video.duration).to.be.greaterThan(1)
expect(video.aspectRatio).to.equal(1.7778)
expect(video.files).to.have.lengthOf(0)
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
await makeRawRequest({ url: hlsPlaylist.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: hlsPlaylist.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
// We should have generated random filenames
expect(basename(hlsPlaylist.playlistUrl)).to.not.equal('master.m3u8')
expect(basename(hlsPlaylist.segmentsSha256Url)).to.not.equal('segments-sha256.json')
expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
for (const resolution of resolutions) {
const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
expect(file).to.exist
expect(file.size).to.be.greaterThan(1)
if (resolution >= 720) {
expect(file.fps).to.be.approximately(60, 10)
} else {
expect(file.fps).to.be.approximately(30, 3)
}
const filename = basename(file.fileUrl)
expect(filename).to.not.contain(video.uuid)
const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename))
const probe = await ffprobePromise(segmentPath)
const videoStream = await getVideoStream(segmentPath, probe)
expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height])
expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height])
await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
}
}
})
it('Should not generate an upper resolution than original file', async function () {
this.timeout(500_000)
const resolutions = [ 240, 480 ]
await updateConf(resolutions)
await servers[0].config.updateExistingConfig({
newConfig: {
live: {
transcoding: {
alwaysTranscodeOriginalResolution: false
}
}
}
})
liveVideoId = await createLiveWrapper(true)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId,
resolutions,
transcoded: true
})
await stopFfmpeg(ffmpegCommand)
await commands[0].waitUntilEnded({ videoId: liveVideoId })
await waitJobs(servers)
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
const video = await servers[0].videos.get({ id: liveVideoId })
const hlsFiles = video.streamingPlaylists[0].files
expect(video.files).to.have.lengthOf(0)
expect(hlsFiles).to.have.lengthOf(resolutions.length)
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
expect(getAllFiles(video).map(f => f.resolution.id).sort()).to.deep.equal(resolutions)
})
it('Should only keep the original resolution if all resolutions are disabled', async function () {
this.timeout(600_000)
await updateConf([])
liveVideoId = await createLiveWrapper(true)
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
await waitJobs(servers)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId,
resolutions: [ 720 ],
transcoded: true
})
await stopFfmpeg(ffmpegCommand)
await commands[0].waitUntilEnded({ videoId: liveVideoId })
await waitJobs(servers)
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
const video = await servers[0].videos.get({ id: liveVideoId })
const hlsFiles = video.streamingPlaylists[0].files
expect(video.files).to.have.lengthOf(0)
expect(hlsFiles).to.have.lengthOf(1)
expect(hlsFiles[0].resolution.id).to.equal(720)
})
after(async function () {
await sqlCommandServer1.cleanup()
})
})
describe('After a server restart', function () {
let liveVideoId: string
let liveVideoReplayId: string
let permanentLiveVideoReplayId: string
let permanentLiveReplayName: string
let beforeServerRestart: Date
async function createLiveWrapper (options: { saveReplay: boolean, permanent: boolean }) {
const liveAttributes: LiveVideoCreate = {
name: 'live video',
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
saveReplay: options.saveReplay,
permanentLive: options.permanent
}
const { uuid } = await commands[0].create({ fields: liveAttributes })
return uuid
}
before(async function () {
this.timeout(600_000)
liveVideoId = await createLiveWrapper({ saveReplay: false, permanent: false })
liveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: false })
permanentLiveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: true })
await Promise.all([
commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }),
commands[0].sendRTMPStreamInVideo({ videoId: permanentLiveVideoReplayId }),
commands[0].sendRTMPStreamInVideo({ videoId: liveVideoReplayId })
])
await Promise.all([
commands[0].waitUntilPublished({ videoId: liveVideoId }),
commands[0].waitUntilPublished({ videoId: permanentLiveVideoReplayId }),
commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
])
for (const videoUUID of [ liveVideoId, liveVideoReplayId, permanentLiveVideoReplayId ]) {
await commands[0].waitUntilSegmentGeneration({
server: servers[0],
videoUUID,
playlistNumber: 0,
segment: 2
})
}
{
const video = await servers[0].videos.get({ id: permanentLiveVideoReplayId })
permanentLiveReplayName = video.name + ' - ' + new Date(video.publishedAt).toLocaleString()
}
await killallServers([ servers[0] ])
beforeServerRestart = new Date()
await servers[0].run()
await wait(5000)
await waitJobs(servers)
})
it('Should cleanup lives', async function () {
this.timeout(60000)
await commands[0].waitUntilEnded({ videoId: liveVideoId })
await commands[0].waitUntilWaiting({ videoId: permanentLiveVideoReplayId })
})
it('Should save a non permanent live replay', async function () {
this.timeout(240000)
await commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
const session = await commands[0].getReplaySession({ videoId: liveVideoReplayId })
expect(session.endDate).to.exist
expect(new Date(session.endDate)).to.be.above(beforeServerRestart)
})
it('Should have saved a permanent live replay', async function () {
this.timeout(120000)
const { data } = await servers[0].videos.listMyVideos({ sort: '-publishedAt' })
expect(data.find(v => v.name === permanentLiveReplayName)).to.exist
})
})
after(async function () {
await cleanupTests(servers)
})
})
+887
ファイルの表示
@@ -0,0 +1,887 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { AbuseMessage, AbusePredefinedReasonsString, AbuseState, AdminAbuse, UserAbuse } from '@peertube/peertube-models'
import {
AbusesCommand,
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
setDefaultAccountAvatar,
setDefaultChannelAvatar,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test abuses', function () {
let servers: PeerTubeServer[] = []
let abuseServer1: AdminAbuse
let abuseServer2: AdminAbuse
let commands: AbusesCommand[]
before(async function () {
this.timeout(120000)
// Run servers
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultChannelAvatar(servers)
await setDefaultAccountAvatar(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
commands = servers.map(s => s.abuses)
})
describe('Video abuses', function () {
before(async function () {
this.timeout(50000)
// Upload some videos on each servers
{
const attributes = {
name: 'my super name for server 1',
description: 'my super description for server 1'
}
await servers[0].videos.upload({ attributes })
}
{
const attributes = {
name: 'my super name for server 2',
description: 'my super description for server 2'
}
await servers[1].videos.upload({ attributes })
}
// Wait videos propagation, server 2 has transcoding enabled
await waitJobs(servers)
const { data } = await servers[0].videos.list()
expect(data.length).to.equal(2)
servers[0].store.videoCreated = data.find(video => video.name === 'my super name for server 1')
servers[1].store.videoCreated = data.find(video => video.name === 'my super name for server 2')
})
it('Should not have abuses', async function () {
const body = await commands[0].getAdminList()
expect(body.total).to.equal(0)
expect(body.data).to.be.an('array')
expect(body.data.length).to.equal(0)
})
it('Should report abuse on a local video', async function () {
this.timeout(15000)
const reason = 'my super bad reason'
await commands[0].report({ videoId: servers[0].store.videoCreated.id, reason })
// We wait requests propagation, even if the server 1 is not supposed to make a request to server 2
await waitJobs(servers)
})
it('Should have 1 video abuses on server 1 and 0 on server 2', async function () {
{
const body = await commands[0].getAdminList()
expect(body.total).to.equal(1)
expect(body.data).to.be.an('array')
expect(body.data.length).to.equal(1)
const abuse = body.data[0]
expect(abuse.reason).to.equal('my super bad reason')
expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video.id).to.equal(servers[0].store.videoCreated.id)
expect(abuse.video.channel).to.exist
expect(abuse.comment).to.be.null
expect(abuse.flaggedAccount.name).to.equal('root')
expect(abuse.flaggedAccount.host).to.equal(servers[0].host)
expect(abuse.video.countReports).to.equal(1)
expect(abuse.video.nthReport).to.equal(1)
expect(abuse.countReportsForReporter).to.equal(1)
expect(abuse.countReportsForReportee).to.equal(1)
}
{
const body = await commands[1].getAdminList()
expect(body.total).to.equal(0)
expect(body.data).to.be.an('array')
expect(body.data.length).to.equal(0)
}
})
it('Should report abuse on a remote video', async function () {
const reason = 'my super bad reason 2'
const videoId = await servers[0].videos.getId({ uuid: servers[1].store.videoCreated.uuid })
await commands[0].report({ videoId, reason })
// We wait requests propagation
await waitJobs(servers)
})
it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
{
const body = await commands[0].getAdminList()
expect(body.total).to.equal(2)
expect(body.data.length).to.equal(2)
const abuse1 = body.data[0]
expect(abuse1.reason).to.equal('my super bad reason')
expect(abuse1.reporterAccount.name).to.equal('root')
expect(abuse1.reporterAccount.host).to.equal(servers[0].host)
expect(abuse1.video.id).to.equal(servers[0].store.videoCreated.id)
expect(abuse1.video.countReports).to.equal(1)
expect(abuse1.video.nthReport).to.equal(1)
expect(abuse1.comment).to.be.null
expect(abuse1.flaggedAccount.name).to.equal('root')
expect(abuse1.flaggedAccount.host).to.equal(servers[0].host)
expect(abuse1.state.id).to.equal(AbuseState.PENDING)
expect(abuse1.state.label).to.equal('Pending')
expect(abuse1.moderationComment).to.be.null
const abuse2 = body.data[1]
expect(abuse2.reason).to.equal('my super bad reason 2')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video.uuid).to.equal(servers[1].store.videoCreated.uuid)
expect(abuse2.comment).to.be.null
expect(abuse2.flaggedAccount.name).to.equal('root')
expect(abuse2.flaggedAccount.host).to.equal(servers[1].host)
expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null
}
{
const body = await commands[1].getAdminList()
expect(body.total).to.equal(1)
expect(body.data.length).to.equal(1)
abuseServer2 = body.data[0]
expect(abuseServer2.reason).to.equal('my super bad reason 2')
expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuseServer2.flaggedAccount.name).to.equal('root')
expect(abuseServer2.flaggedAccount.host).to.equal(servers[1].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null
}
})
it('Should hide video abuses from blocked accounts', async function () {
{
const videoId = await servers[1].videos.getId({ uuid: servers[0].store.videoCreated.uuid })
await commands[1].report({ videoId, reason: 'will mute this' })
await waitJobs(servers)
const body = await commands[0].getAdminList()
expect(body.total).to.equal(3)
}
const accountToBlock = 'root@' + servers[1].host
{
await servers[0].blocklist.addToServerBlocklist({ account: accountToBlock })
const body = await commands[0].getAdminList()
expect(body.total).to.equal(2)
const abuse = body.data.find(a => a.reason === 'will mute this')
expect(abuse).to.be.undefined
}
{
await servers[0].blocklist.removeFromServerBlocklist({ account: accountToBlock })
const body = await commands[0].getAdminList()
expect(body.total).to.equal(3)
}
})
it('Should hide video abuses from blocked servers', async function () {
const serverToBlock = servers[1].host
{
await servers[0].blocklist.addToServerBlocklist({ server: serverToBlock })
const body = await commands[0].getAdminList()
expect(body.total).to.equal(2)
const abuse = body.data.find(a => a.reason === 'will mute this')
expect(abuse).to.be.undefined
}
{
await servers[0].blocklist.removeFromServerBlocklist({ server: serverToBlock })
const body = await commands[0].getAdminList()
expect(body.total).to.equal(3)
}
})
it('Should keep the video abuse when deleting the video', async function () {
await servers[1].videos.remove({ id: abuseServer2.video.uuid })
await waitJobs(servers)
const body = await commands[1].getAdminList()
expect(body.total).to.equal(2, 'wrong number of videos returned')
expect(body.data).to.have.lengthOf(2, 'wrong number of videos returned')
const abuse = body.data[0]
expect(abuse.id).to.equal(abuseServer2.id, 'wrong origin server id for first video')
expect(abuse.video.id).to.equal(abuseServer2.video.id, 'wrong video id')
expect(abuse.video.channel).to.exist
expect(abuse.video.deleted).to.be.true
})
it('Should include counts of reports from reporter and reportee', async function () {
// register a second user to have two reporters/reportees
const user = { username: 'user2', password: 'password' }
await servers[0].users.create({ ...user })
const userAccessToken = await servers[0].login.getAccessToken(user)
// upload a third video via this user
const attributes = {
name: 'my second super name for server 1',
description: 'my second super description for server 1'
}
const { id } = await servers[0].videos.upload({ token: userAccessToken, attributes })
const video3Id = id
// resume with the test
const reason3 = 'my super bad reason 3'
await commands[0].report({ videoId: video3Id, reason: reason3 })
const reason4 = 'my super bad reason 4'
await commands[0].report({ token: userAccessToken, videoId: servers[0].store.videoCreated.id, reason: reason4 })
{
const body = await commands[0].getAdminList()
const abuses = body.data
const abuseVideo3 = body.data.find(a => a.video.id === video3Id)
expect(abuseVideo3).to.not.be.undefined
expect(abuseVideo3.video.countReports).to.equal(1, 'wrong reports count for video 3')
expect(abuseVideo3.video.nthReport).to.equal(1, 'wrong report position in report list for video 3')
expect(abuseVideo3.countReportsForReportee).to.equal(1, 'wrong reports count for reporter on video 3 abuse')
expect(abuseVideo3.countReportsForReporter).to.equal(3, 'wrong reports count for reportee on video 3 abuse')
const abuseServer1 = abuses.find(a => a.video.id === servers[0].store.videoCreated.id)
expect(abuseServer1.countReportsForReportee).to.equal(3, 'wrong reports count for reporter on video 1 abuse')
}
})
it('Should list predefined reasons as well as timestamps for the reported video', async function () {
const reason5 = 'my super bad reason 5'
const predefinedReasons5: AbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ]
const createRes = await commands[0].report({
videoId: servers[0].store.videoCreated.id,
reason: reason5,
predefinedReasons: predefinedReasons5,
startAt: 1,
endAt: 5
})
const body = await commands[0].getAdminList()
{
const abuse = body.data.find(a => a.id === createRes.abuse.id)
expect(abuse.reason).to.equals(reason5)
expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, 'predefined reasons do not match the one reported')
expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported")
expect(abuse.video.endAt).to.equal(5, "ending timestamp doesn't match the one reported")
}
})
it('Should delete the video abuse', async function () {
await commands[1].delete({ abuseId: abuseServer2.id })
await waitJobs(servers)
{
const body = await commands[1].getAdminList()
expect(body.total).to.equal(1)
expect(body.data.length).to.equal(1)
expect(body.data[0].id).to.not.equal(abuseServer2.id)
}
{
const body = await commands[0].getAdminList()
expect(body.total).to.equal(6)
}
})
it('Should list and filter video abuses', async function () {
async function list (query: Parameters<AbusesCommand['getAdminList']>[0]) {
const body = await commands[0].getAdminList(query)
return body.data
}
expect(await list({ id: 56 })).to.have.lengthOf(0)
expect(await list({ id: 1 })).to.have.lengthOf(1)
expect(await list({ search: 'my super name for server 1' })).to.have.lengthOf(4)
expect(await list({ search: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' })).to.have.lengthOf(0)
expect(await list({ searchVideo: 'my second super name for server 1' })).to.have.lengthOf(1)
expect(await list({ searchVideoChannel: 'root' })).to.have.lengthOf(4)
expect(await list({ searchVideoChannel: 'aaaa' })).to.have.lengthOf(0)
expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1)
expect(await list({ searchReporter: 'root' })).to.have.lengthOf(5)
expect(await list({ searchReportee: 'root' })).to.have.lengthOf(5)
expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0)
expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1)
expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0)
expect(await list({ state: AbuseState.ACCEPTED })).to.have.lengthOf(0)
expect(await list({ state: AbuseState.PENDING })).to.have.lengthOf(6)
expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1)
expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0)
})
})
describe('Comment abuses', function () {
async function getComment (server: PeerTubeServer, videoIdArg: number | string) {
const videoId = typeof videoIdArg === 'string'
? await server.videos.getId({ uuid: videoIdArg })
: videoIdArg
const { data } = await server.comments.listThreads({ videoId })
return data[0]
}
before(async function () {
this.timeout(50000)
servers[0].store.videoCreated = await servers[0].videos.quickUpload({ name: 'server 1' })
servers[1].store.videoCreated = await servers[1].videos.quickUpload({ name: 'server 2' })
await servers[0].comments.createThread({ videoId: servers[0].store.videoCreated.id, text: 'comment server 1' })
await servers[1].comments.createThread({ videoId: servers[1].store.videoCreated.id, text: 'comment server 2' })
await waitJobs(servers)
})
it('Should report abuse on a comment', async function () {
this.timeout(15000)
const comment = await getComment(servers[0], servers[0].store.videoCreated.id)
const reason = 'it is a bad comment'
await commands[0].report({ commentId: comment.id, reason })
await waitJobs(servers)
})
it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () {
{
const comment = await getComment(servers[0], servers[0].store.videoCreated.id)
const body = await commands[0].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(1)
expect(body.data).to.have.lengthOf(1)
const abuse = body.data[0]
expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video).to.be.null
expect(abuse.comment.deleted).to.be.false
expect(abuse.comment.id).to.equal(comment.id)
expect(abuse.comment.text).to.equal(comment.text)
expect(abuse.comment.video.name).to.equal('server 1')
expect(abuse.comment.video.id).to.equal(servers[0].store.videoCreated.id)
expect(abuse.comment.video.uuid).to.equal(servers[0].store.videoCreated.uuid)
expect(abuse.countReportsForReporter).to.equal(5)
expect(abuse.countReportsForReportee).to.equal(5)
}
{
const body = await commands[1].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(0)
expect(body.data.length).to.equal(0)
}
})
it('Should report abuse on a remote comment', async function () {
const comment = await getComment(servers[0], servers[1].store.videoCreated.uuid)
const reason = 'it is a really bad comment'
await commands[0].report({ commentId: comment.id, reason })
await waitJobs(servers)
})
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.shortUUID)
{
const body = await commands[0].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(2)
expect(body.data.length).to.equal(2)
const abuse = body.data[0]
expect(abuse.reason).to.equal('it is a bad comment')
expect(abuse.countReportsForReporter).to.equal(6)
expect(abuse.countReportsForReportee).to.equal(5)
const abuse2 = body.data[1]
expect(abuse2.reason).to.equal('it is a really bad comment')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video).to.be.null
expect(abuse2.comment.deleted).to.be.false
expect(abuse2.comment.id).to.equal(commentServer2.id)
expect(abuse2.comment.text).to.equal(commentServer2.text)
expect(abuse2.comment.video.name).to.equal('server 2')
expect(abuse2.comment.video.uuid).to.equal(servers[1].store.videoCreated.uuid)
expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null
expect(abuse2.countReportsForReporter).to.equal(6)
expect(abuse2.countReportsForReportee).to.equal(2)
}
{
const body = await commands[1].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(1)
expect(body.data.length).to.equal(1)
abuseServer2 = body.data[0]
expect(abuseServer2.reason).to.equal('it is a really bad comment')
expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null
expect(abuseServer2.countReportsForReporter).to.equal(1)
expect(abuseServer2.countReportsForReportee).to.equal(1)
}
})
it('Should keep the comment abuse when deleting the comment', async function () {
const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.uuid)
await servers[0].comments.delete({ videoId: servers[1].store.videoCreated.uuid, commentId: commentServer2.id })
await waitJobs(servers)
const body = await commands[0].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(2)
expect(body.data).to.have.lengthOf(2)
const abuse = body.data.find(a => a.comment?.id === commentServer2.id)
expect(abuse).to.not.be.undefined
expect(abuse.comment.text).to.be.empty
expect(abuse.comment.video.name).to.equal('server 2')
expect(abuse.comment.deleted).to.be.true
})
it('Should delete the comment abuse', async function () {
await commands[1].delete({ abuseId: abuseServer2.id })
await waitJobs(servers)
{
const body = await commands[1].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(0)
expect(body.data.length).to.equal(0)
}
{
const body = await commands[0].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(2)
}
})
it('Should list and filter video abuses', async function () {
{
const body = await commands[0].getAdminList({ filter: 'comment', searchReportee: 'foo' })
expect(body.total).to.equal(0)
}
{
const body = await commands[0].getAdminList({ filter: 'comment', searchReportee: 'ot' })
expect(body.total).to.equal(2)
}
{
const body = await commands[0].getAdminList({ filter: 'comment', start: 1, count: 1, sort: 'createdAt' })
expect(body.data).to.have.lengthOf(1)
expect(body.data[0].comment.text).to.be.empty
}
{
const body = await commands[0].getAdminList({ filter: 'comment', start: 1, count: 1, sort: '-createdAt' })
expect(body.data).to.have.lengthOf(1)
expect(body.data[0].comment.text).to.equal('comment server 1')
}
})
})
describe('Account abuses', function () {
function getAccountFromServer (server: PeerTubeServer, targetName: string, targetServer: PeerTubeServer) {
return server.accounts.get({ accountName: targetName + '@' + targetServer.host })
}
before(async function () {
this.timeout(50000)
await servers[0].users.create({ username: 'user_1', password: 'donald' })
const token = await servers[1].users.generateUserAndToken('user_2')
await servers[1].videos.upload({ token, attributes: { name: 'super video' } })
await waitJobs(servers)
})
it('Should report abuse on an account', async function () {
this.timeout(15000)
const account = await getAccountFromServer(servers[0], 'user_1', servers[0])
const reason = 'it is a bad account'
await commands[0].report({ accountId: account.id, reason })
await waitJobs(servers)
})
it('Should have 1 account abuse on server 1 and 0 on server 2', async function () {
{
const body = await commands[0].getAdminList({ filter: 'account' })
expect(body.total).to.equal(1)
expect(body.data).to.have.lengthOf(1)
const abuse = body.data[0]
expect(abuse.reason).to.equal('it is a bad account')
expect(abuse.reporterAccount.name).to.equal('root')
expect(abuse.reporterAccount.host).to.equal(servers[0].host)
expect(abuse.video).to.be.null
expect(abuse.comment).to.be.null
expect(abuse.flaggedAccount.name).to.equal('user_1')
expect(abuse.flaggedAccount.host).to.equal(servers[0].host)
}
{
const body = await commands[1].getAdminList({ filter: 'comment' })
expect(body.total).to.equal(0)
expect(body.data.length).to.equal(0)
}
})
it('Should report abuse on a remote account', async function () {
const account = await getAccountFromServer(servers[0], 'user_2', servers[1])
const reason = 'it is a really bad account'
await commands[0].report({ accountId: account.id, reason })
await waitJobs(servers)
})
it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
{
const body = await commands[0].getAdminList({ filter: 'account' })
expect(body.total).to.equal(2)
expect(body.data.length).to.equal(2)
const abuse: AdminAbuse = body.data[0]
expect(abuse.reason).to.equal('it is a bad account')
const abuse2: AdminAbuse = body.data[1]
expect(abuse2.reason).to.equal('it is a really bad account')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal(servers[0].host)
expect(abuse2.video).to.be.null
expect(abuse2.comment).to.be.null
expect(abuse2.state.id).to.equal(AbuseState.PENDING)
expect(abuse2.state.label).to.equal('Pending')
expect(abuse2.moderationComment).to.be.null
}
{
const body = await commands[1].getAdminList({ filter: 'account' })
expect(body.total).to.equal(1)
expect(body.data.length).to.equal(1)
abuseServer2 = body.data[0]
expect(abuseServer2.reason).to.equal('it is a really bad account')
expect(abuseServer2.reporterAccount.name).to.equal('root')
expect(abuseServer2.reporterAccount.host).to.equal(servers[0].host)
expect(abuseServer2.state.id).to.equal(AbuseState.PENDING)
expect(abuseServer2.state.label).to.equal('Pending')
expect(abuseServer2.moderationComment).to.be.null
}
})
it('Should keep the account abuse when deleting the account', async function () {
const account = await getAccountFromServer(servers[1], 'user_2', servers[1])
await servers[1].users.remove({ userId: account.userId })
await waitJobs(servers)
const body = await commands[0].getAdminList({ filter: 'account' })
expect(body.total).to.equal(2)
expect(body.data).to.have.lengthOf(2)
const abuse = body.data.find(a => a.reason === 'it is a really bad account')
expect(abuse).to.not.be.undefined
})
it('Should delete the account abuse', async function () {
await commands[1].delete({ abuseId: abuseServer2.id })
await waitJobs(servers)
{
const body = await commands[1].getAdminList({ filter: 'account' })
expect(body.total).to.equal(0)
expect(body.data.length).to.equal(0)
}
{
const body = await commands[0].getAdminList({ filter: 'account' })
expect(body.total).to.equal(2)
abuseServer1 = body.data[0]
}
})
})
describe('Common actions on abuses', function () {
it('Should update the state of an abuse', async function () {
await commands[0].update({ abuseId: abuseServer1.id, body: { state: AbuseState.REJECTED } })
const body = await commands[0].getAdminList({ id: abuseServer1.id })
expect(body.data[0].state.id).to.equal(AbuseState.REJECTED)
})
it('Should add a moderation comment', async function () {
await commands[0].update({ abuseId: abuseServer1.id, body: { state: AbuseState.ACCEPTED, moderationComment: 'Valid' } })
const body = await commands[0].getAdminList({ id: abuseServer1.id })
expect(body.data[0].state.id).to.equal(AbuseState.ACCEPTED)
expect(body.data[0].moderationComment).to.equal('Valid')
})
})
describe('My abuses', async function () {
let abuseId1: number
let userAccessToken: string
before(async function () {
userAccessToken = await servers[0].users.generateUserAndToken('user_42')
await commands[0].report({ token: userAccessToken, videoId: servers[0].store.videoCreated.id, reason: 'user reason 1' })
const videoId = await servers[0].videos.getId({ uuid: servers[1].store.videoCreated.uuid })
await commands[0].report({ token: userAccessToken, videoId, reason: 'user reason 2' })
})
it('Should correctly list my abuses', async function () {
{
const body = await commands[0].getUserList({ token: userAccessToken, start: 0, count: 5, sort: 'createdAt' })
expect(body.total).to.equal(2)
const abuses = body.data
expect(abuses[0].reason).to.equal('user reason 1')
expect(abuses[1].reason).to.equal('user reason 2')
abuseId1 = abuses[0].id
}
{
const body = await commands[0].getUserList({ token: userAccessToken, start: 1, count: 1, sort: 'createdAt' })
expect(body.total).to.equal(2)
const abuses: UserAbuse[] = body.data
expect(abuses[0].reason).to.equal('user reason 2')
}
{
const body = await commands[0].getUserList({ token: userAccessToken, start: 1, count: 1, sort: '-createdAt' })
expect(body.total).to.equal(2)
const abuses: UserAbuse[] = body.data
expect(abuses[0].reason).to.equal('user reason 1')
}
})
it('Should correctly filter my abuses by id', async function () {
const body = await commands[0].getUserList({ token: userAccessToken, id: abuseId1 })
expect(body.total).to.equal(1)
const abuses: UserAbuse[] = body.data
expect(abuses[0].reason).to.equal('user reason 1')
})
it('Should correctly filter my abuses by search', async function () {
const body = await commands[0].getUserList({ token: userAccessToken, search: 'server 2' })
expect(body.total).to.equal(1)
const abuses: UserAbuse[] = body.data
expect(abuses[0].reason).to.equal('user reason 2')
})
it('Should correctly filter my abuses by state', async function () {
await commands[0].update({ abuseId: abuseId1, body: { state: AbuseState.REJECTED } })
const body = await commands[0].getUserList({ token: userAccessToken, state: AbuseState.REJECTED })
expect(body.total).to.equal(1)
const abuses: UserAbuse[] = body.data
expect(abuses[0].reason).to.equal('user reason 1')
})
})
describe('Abuse messages', async function () {
let abuseId: number
let userToken: string
let abuseMessageUserId: number
let abuseMessageModerationId: number
before(async function () {
userToken = await servers[0].users.generateUserAndToken('user_43')
const body = await commands[0].report({ token: userToken, videoId: servers[0].store.videoCreated.id, reason: 'user 43 reason 1' })
abuseId = body.abuse.id
})
it('Should create some messages on the abuse', async function () {
await commands[0].addMessage({ token: userToken, abuseId, message: 'message 1' })
await commands[0].addMessage({ abuseId, message: 'message 2' })
await commands[0].addMessage({ abuseId, message: 'message 3' })
await commands[0].addMessage({ token: userToken, abuseId, message: 'message 4' })
})
it('Should have the correct messages count when listing abuses', async function () {
const results = await Promise.all([
commands[0].getAdminList({ start: 0, count: 50 }),
commands[0].getUserList({ token: userToken, start: 0, count: 50 })
])
for (const body of results) {
const abuses = body.data
const abuse = abuses.find(a => a.id === abuseId)
expect(abuse.countMessages).to.equal(4)
}
})
it('Should correctly list messages of this abuse', async function () {
const results = await Promise.all([
commands[0].listMessages({ abuseId }),
commands[0].listMessages({ token: userToken, abuseId })
])
for (const body of results) {
expect(body.total).to.equal(4)
const abuseMessages: AbuseMessage[] = body.data
expect(abuseMessages[0].message).to.equal('message 1')
expect(abuseMessages[0].byModerator).to.be.false
expect(abuseMessages[0].account.name).to.equal('user_43')
abuseMessageUserId = abuseMessages[0].id
expect(abuseMessages[1].message).to.equal('message 2')
expect(abuseMessages[1].byModerator).to.be.true
expect(abuseMessages[1].account.name).to.equal('root')
expect(abuseMessages[2].message).to.equal('message 3')
expect(abuseMessages[2].byModerator).to.be.true
expect(abuseMessages[2].account.name).to.equal('root')
abuseMessageModerationId = abuseMessages[2].id
expect(abuseMessages[3].message).to.equal('message 4')
expect(abuseMessages[3].byModerator).to.be.false
expect(abuseMessages[3].account.name).to.equal('user_43')
}
})
it('Should delete messages', async function () {
await commands[0].deleteMessage({ abuseId, messageId: abuseMessageModerationId })
await commands[0].deleteMessage({ token: userToken, abuseId, messageId: abuseMessageUserId })
const results = await Promise.all([
commands[0].listMessages({ abuseId }),
commands[0].listMessages({ token: userToken, abuseId })
])
for (const body of results) {
expect(body.total).to.equal(2)
const abuseMessages: AbuseMessage[] = body.data
expect(abuseMessages[0].message).to.equal('message 2')
expect(abuseMessages[1].message).to.equal('message 4')
}
})
})
after(async function () {
await cleanupTests(servers)
})
})
+489
ファイルの表示
@@ -0,0 +1,489 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests, createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
setDefaultAccountAvatar,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { expect } from 'chai'
describe('Test automatic tags', function () {
let servers: PeerTubeServer[]
let videoUUID: string
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await setDefaultAccountAvatar(servers)
await servers[1].config.enableLive({ allowReplay: false })
await doubleFollow(servers[0], servers[1]);
({ uuid: videoUUID } = await servers[0].videos.quickUpload({ name: 'video' }))
await waitJobs(servers)
})
describe('Automatic tags on comments', function () {
describe('Built in external link auto tag', function () {
it('Should not assign external-link automatic tag with no URL inside the comment', async function () {
const tests = [
'my super comment',
'toto.azfazfe',
'Hello. Hi friends'
]
for (const toTest of tests) {
await servers[0].comments.createThread({ videoId: videoUUID, text: toTest })
await waitJobs(servers)
}
for (const server of servers) {
const { data } = await server.comments.listForAdmin()
for (const comment of data) {
expect(comment.automaticTags, `"${comment.text}" has an automatic tag`).to.have.lengthOf(0)
}
}
await servers[0].comments.deleteAllComments({ videoUUID })
await waitJobs(servers)
})
it('Should not assign external-link automatic tag if the URL is an internal link', async function () {
const tests = [
`Hi ${servers[0].url}`
]
for (const toTest of tests) {
await servers[0].comments.createThread({ videoId: videoUUID, text: toTest })
await waitJobs(servers)
}
// Server 1
{
const { data } = await servers[0].comments.listForAdmin()
for (const comment of data) {
expect(comment.automaticTags, `"${comment.text}" has an automatic tag`).to.have.lengthOf(0)
}
}
// Server 2
{
const { data } = await servers[1].comments.listForAdmin()
for (const comment of data) {
expect(comment.automaticTags, `"${comment.text}" hasn't an automatic tag`).to.have.lengthOf(1)
expect(comment.automaticTags[0]).to.equal('external-link')
}
}
await servers[0].comments.deleteAllComments({ videoUUID })
await waitJobs(servers)
})
it('Should assign external-link automatic tag', async function () {
const tests = [
'example.com',
'Hi example.com'
]
for (const toTest of tests) {
await servers[0].comments.createThread({ videoId: videoUUID, text: toTest })
await waitJobs(servers)
}
for (const server of servers) {
const { data } = await server.comments.listForAdmin()
for (const comment of data) {
expect(comment.automaticTags).to.have.lengthOf(1)
expect(comment.automaticTags[0]).to.equal('external-link')
}
}
await servers[0].comments.deleteAllComments({ videoUUID })
await waitJobs(servers)
})
})
describe('With watched words', function () {
let accountListId: number
it('Should create watched words list and automatically assign an automatic tag', async function () {
// Account list
{
await servers[0].watchedWordsLists.createList({ listName: 'list 1', words: [ 'word 1', 'word 2' ], accountName: 'root' })
const { watchedWordsList } = await servers[0].watchedWordsLists.createList({
listName: 'list 2',
words: [ 'nemo' ],
accountName: 'root'
})
accountListId = watchedWordsList.id
}
// Server list
{
await servers[0].watchedWordsLists.createList({ listName: 'server 2', words: [ 'word 2' ] })
}
await servers[0].comments.createThread({ videoId: videoUUID, text: 'hi captain' })
await servers[0].comments.addReplyToLastThread({ text: 'hi captain nemo' })
await servers[1].comments.createThread({ videoId: videoUUID, text: 'hi captain nemo word 2 example.com' })
await waitJobs(servers)
// Server comments list must not include account personal watched words
{
const { data } = await servers[0].comments.listForAdmin()
const c = (text: string) => data.find(c => c.text === text)
expect(c('hi captain').automaticTags).to.have.lengthOf(0)
expect(c('hi captain nemo').automaticTags).to.have.lengthOf(0)
expect(c('hi captain nemo word 2 example.com').automaticTags).to.have.members([ 'server 2', 'external-link' ])
}
{
const { data } = await servers[0].comments.listCommentsOnMyVideos()
const c = (text: string) => data.find(c => c.text === text)
expect(c('hi captain').automaticTags).to.have.lengthOf(0)
expect(c('hi captain nemo').automaticTags).to.have.members([ 'list 2' ])
expect(c('hi captain nemo word 2 example.com').automaticTags).to.have.members([ 'list 1', 'list 2', 'external-link' ])
}
})
it('Should update watched words list and assign auto tag with new words', async function () {
// No tags
{
await servers[0].comments.createThread({ videoId: videoUUID, text: 'my nautilus' })
const { data } = await servers[0].comments.listCommentsOnMyVideos()
expect(data.find(c => c.text === 'my nautilus').automaticTags).to.have.lengthOf(0)
}
{
await servers[0].watchedWordsLists.updateList({
accountName: 'root',
listId: accountListId,
words: [ 'nautilus' ],
listName: 'list 3'
})
await servers[0].comments.createThread({ videoId: videoUUID, text: 'captain nemo' })
await servers[0].comments.createThread({ videoId: videoUUID, text: 'my nautilus 2' })
await servers[0].comments.createThread({ videoId: videoUUID, text: 'word 1' })
const { data } = await servers[0].comments.listCommentsOnMyVideos()
// Previous comment still have the same automatic tags
expect(data.find(c => c.text === 'my nautilus').automaticTags).to.have.lengthOf(0)
expect(data.find(c => c.text === 'captain nemo').automaticTags).to.have.lengthOf(0)
expect(data.find(c => c.text === 'my nautilus 2').automaticTags).to.have.members([ 'list 3' ])
expect(data.find(c => c.text === 'word 1').automaticTags).to.have.members([ 'list 1' ])
}
})
it('Should delete watched words list and so not assign auto tags anymore', async function () {
await servers[0].watchedWordsLists.deleteList({ accountName: 'root', listId: accountListId })
await servers[0].comments.createThread({ videoId: videoUUID, text: 'my nautilus 3' })
await servers[0].comments.createThread({ videoId: videoUUID, text: 'word 2' })
const { data } = await servers[0].comments.listCommentsOnMyVideos()
expect(data.find(c => c.text === 'my nautilus 3').automaticTags).to.have.lengthOf(0)
expect(data.find(c => c.text === 'word 2').automaticTags).to.have.members([ 'list 1' ])
})
})
describe('Searching comments with specific tags', function () {
it('Should search in "comments on my videos" comments with specific automatic tags', async function () {
{
const { total, data } = await servers[0].comments.listCommentsOnMyVideos({ autoTagOneOf: [ 'unknown' ] })
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
}
{
for (const autoTagOneOf of [ [ 'list 1' ], [ 'list 1', 'unknown' ] ]) {
const { total, data } = await servers[0].comments.listCommentsOnMyVideos({ autoTagOneOf })
expect(total).to.equal(3)
expect(data.map(c => c.text)).to.have.members([
'hi captain nemo word 2 example.com',
'word 1',
'word 2'
])
}
}
})
it('Should search in admin comments with specific automatic tags', async function () {
{
const { total, data } = await servers[0].comments.listForAdmin({ autoTagOneOf: [ 'list 1' ] })
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
}
{
const { total, data } = await servers[0].comments.listForAdmin({ autoTagOneOf: [ 'external-link' ] })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].text).to.equal('hi captain nemo word 2 example.com')
}
})
})
})
describe('Automatic tags on videos', function () {
before(async function () {
await servers[0].videos.removeAll()
await waitJobs(servers)
})
describe('Built in external link auto tag', function () {
it('Should not assign external-link automatic tag with no URL inside the video', async function () {
const tests = [
'my super video',
'toto.azfazfe',
'Hello. Hi friends'
]
for (const toTest of tests) {
await servers[0].videos.upload({ attributes: { name: toTest, description: toTest } })
await waitJobs(servers)
}
for (const server of servers) {
const { data } = await server.videos.listAllForAdmin()
for (const video of data) {
expect(video.automaticTags, `"${video.name}" has an automatic tag`).to.have.lengthOf(0)
}
}
await servers[0].videos.removeAll()
await waitJobs(servers)
})
it('Should not assign external-link automatic tag if the URL is an internal link', async function () {
const tests = [
`Hi ${servers[0].url}`
]
for (const toTest of tests) {
await servers[0].videos.upload({ attributes: { name: toTest, description: toTest } })
await waitJobs(servers)
}
// Server 1
{
const { data } = await servers[0].videos.listAllForAdmin()
for (const video of data) {
expect(video.automaticTags, `"${video.name}" has an automatic tag`).to.have.lengthOf(0)
}
}
// Server 2
{
const { data } = await servers[1].videos.listAllForAdmin()
for (const video of data) {
expect(video.automaticTags, `"${video.name}" hasn't an automatic tag`).to.have.lengthOf(1)
expect(video.automaticTags[0]).to.equal('external-link')
}
}
await servers[0].videos.removeAll()
await waitJobs(servers)
})
it('Should assign external-link automatic tag', async function () {
const tests = [
'example.com',
'Hi example.com'
]
for (const toTest of tests) {
await servers[0].videos.upload({ attributes: { name: toTest, description: toTest } })
await waitJobs(servers)
}
for (const server of servers) {
const { data } = await server.videos.listAllForAdmin()
for (const video of data) {
expect(video.automaticTags).to.have.lengthOf(1)
expect(video.automaticTags[0]).to.equal('external-link')
}
}
await servers[0].videos.removeAll()
await waitJobs(servers)
})
})
describe('With watched words', function () {
let serverListId: number
let liveUUID: string
it('Should create watched words list and automatically assign an automatic tag', async function () {
// Server list
{
await servers[0].watchedWordsLists.createList({
listName: 'donald list',
words: [ 'riri', 'fifi', 'loulou' ]
})
const { watchedWordsList } = await servers[0].watchedWordsLists.createList({
listName: 'mickey list',
words: [ 'dingo', 'pluto' ]
})
serverListId = watchedWordsList.id
}
// Account list
{
await servers[0].watchedWordsLists.createList({ listName: 'picsou list', words: [ 'goldie' ], accountName: 'root' })
}
await servers[0].videos.upload({ attributes: { name: 'my dear goldie', description: 'hi riri and fifi' } })
await servers[0].videoImports.importVideo({
attributes: {
targetUrl: FIXTURE_URLS.goodVideo,
channelId: servers[0].store.channel.id,
name: 'import video',
description: 'pluto dog'
}
})
const { uuid } = await servers[1].live.create({
fields: {
channelId: servers[0].store.channel.id,
privacy: VideoPrivacy.PUBLIC,
name: 'live loulou',
description: 'dingo and minnie'
}
})
liveUUID = uuid
await waitJobs(servers)
// Server videos list must not include account personal watched words
{
const { data } = await servers[0].videos.listAllForAdmin()
const v = (name: string) => data.find(c => c.name === name)
expect(v('my dear goldie').automaticTags).to.have.members([ 'donald list' ])
expect(v('import video').automaticTags).to.have.members([ 'mickey list' ])
expect(v('live loulou').automaticTags).to.have.members([ 'donald list', 'mickey list' ])
}
{
const { data } = await servers[0].videos.listMyVideos()
for (const video of data) {
expect(video.automaticTags).to.not.exist
}
}
})
it('Should update watched words list and assign auto tag on update', async function () {
const { uuid } = await servers[0].videos.quickUpload({ name: 'hi minnie' })
{
const { data } = await servers[0].videos.listAllForAdmin()
expect(data.find(v => v.name === 'hi minnie').automaticTags).to.have.lengthOf(0)
}
{
await servers[0].watchedWordsLists.updateList({
listId: serverListId,
words: [ 'Minnie' ],
listName: 'mickey list v2'
})
await servers[0].videos.update({ id: uuid, attributes: { name: 'hi minnie v2' } })
const { data } = await servers[0].videos.listAllForAdmin()
expect(data.find(v => v.name === 'hi minnie v2').automaticTags).to.have.members([ 'mickey list v2' ])
}
})
it('Should not update remote video if name/description has not changed', async function () {
await servers[1].videos.update({
id: liveUUID,
attributes: {
channelId: servers[0].store.channel.id,
tags: [ 'super tag' ]
}
})
await waitJobs(servers)
const { data } = await servers[0].videos.listAllForAdmin()
expect(data.find(v => v.name === 'live loulou').automaticTags).to.have.members([ 'donald list', 'mickey list' ])
})
it('Should update remote video if name/description has changed', async function () {
await servers[1].videos.update({
id: liveUUID,
attributes: { name: 'live loulou v2' }
})
await waitJobs(servers)
const { data } = await servers[0].videos.listAllForAdmin()
expect(data.find(v => v.name === 'live loulou v2').automaticTags).to.have.members([ 'donald list', 'mickey list v2' ])
})
})
describe('Searching videos with specific tags', function () {
it('Should search in admin videos with specific automatic tags', async function () {
{
const { total, data } = await servers[0].videos.listAllForAdmin({ autoTagOneOf: [ 'picsou list' ] })
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
}
{
const { total, data } = await servers[0].videos.listAllForAdmin({ autoTagOneOf: [ 'mickey list v2' ] })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
expect(data.map(d => d.name)).to.have.members([ 'hi minnie v2', 'live loulou v2' ])
}
})
})
})
after(async function () {
await cleanupTests(servers)
})
})
+231
ファイルの表示
@@ -0,0 +1,231 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { UserNotificationType, UserNotificationType_Type } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
async function checkNotifications (server: PeerTubeServer, token: string, expected: UserNotificationType_Type[]) {
const { data } = await server.notifications.list({ token, start: 0, count: 10, unread: true })
expect(data).to.have.lengthOf(expected.length)
for (const type of expected) {
expect(data.find(n => n.type === type)).to.exist
}
}
describe('Test blocklist notifications', function () {
let servers: PeerTubeServer[]
let videoUUID: string
let userToken1: string
let userToken2: string
let remoteUserToken: string
async function resetState () {
try {
await servers[1].subscriptions.remove({ token: remoteUserToken, uri: 'user1_channel@' + servers[0].host })
await servers[1].subscriptions.remove({ token: remoteUserToken, uri: 'user2_channel@' + servers[0].host })
} catch {}
await waitJobs(servers)
await servers[0].notifications.markAsReadAll({ token: userToken1 })
await servers[0].notifications.markAsReadAll({ token: userToken2 })
{
const { uuid } = await servers[0].videos.upload({ token: userToken1, attributes: { name: 'video' } })
videoUUID = uuid
await waitJobs(servers)
}
{
await servers[1].comments.createThread({
token: remoteUserToken,
videoId: videoUUID,
text: '@user2@' + servers[0].host + ' hello'
})
}
{
await servers[1].subscriptions.add({ token: remoteUserToken, targetUri: 'user1_channel@' + servers[0].host })
await servers[1].subscriptions.add({ token: remoteUserToken, targetUri: 'user2_channel@' + servers[0].host })
}
await waitJobs(servers)
}
before(async function () {
this.timeout(60000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
{
const user = { username: 'user1', password: 'password' }
await servers[0].users.create({
username: user.username,
password: user.password,
videoQuota: -1,
videoQuotaDaily: -1
})
userToken1 = await servers[0].login.getAccessToken(user)
await servers[0].videos.upload({ token: userToken1, attributes: { name: 'video user 1' } })
}
{
const user = { username: 'user2', password: 'password' }
await servers[0].users.create({ username: user.username, password: user.password })
userToken2 = await servers[0].login.getAccessToken(user)
}
{
const user = { username: 'user3', password: 'password' }
await servers[1].users.create({ username: user.username, password: user.password })
remoteUserToken = await servers[1].login.getAccessToken(user)
}
await doubleFollow(servers[0], servers[1])
})
describe('User blocks another user', function () {
before(async function () {
this.timeout(30000)
await resetState()
})
it('Should have appropriate notifications', async function () {
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken1, notifs)
})
it('Should block an account', async function () {
await servers[0].blocklist.addToMyBlocklist({ token: userToken1, account: 'user3@' + servers[1].host })
await waitJobs(servers)
})
it('Should not have notifications from this account', async function () {
await checkNotifications(servers[0], userToken1, [])
})
it('Should have notifications of this account on user 2', async function () {
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken2, notifs)
await servers[0].blocklist.removeFromMyBlocklist({ token: userToken1, account: 'user3@' + servers[1].host })
})
})
describe('User blocks another server', function () {
before(async function () {
this.timeout(30000)
await resetState()
})
it('Should have appropriate notifications', async function () {
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken1, notifs)
})
it('Should block an account', async function () {
await servers[0].blocklist.addToMyBlocklist({ token: userToken1, server: servers[1].host })
await waitJobs(servers)
})
it('Should not have notifications from this account', async function () {
await checkNotifications(servers[0], userToken1, [])
})
it('Should have notifications of this account on user 2', async function () {
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken2, notifs)
await servers[0].blocklist.removeFromMyBlocklist({ token: userToken1, server: servers[1].host })
})
})
describe('Server blocks a user', function () {
before(async function () {
this.timeout(30000)
await resetState()
})
it('Should have appropriate notifications', async function () {
{
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken1, notifs)
}
{
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken2, notifs)
}
})
it('Should block an account', async function () {
await servers[0].blocklist.addToServerBlocklist({ account: 'user3@' + servers[1].host })
await waitJobs(servers)
})
it('Should not have notifications from this account', async function () {
await checkNotifications(servers[0], userToken1, [])
await checkNotifications(servers[0], userToken2, [])
await servers[0].blocklist.removeFromServerBlocklist({ account: 'user3@' + servers[1].host })
})
})
describe('Server blocks a server', function () {
before(async function () {
this.timeout(30000)
await resetState()
})
it('Should have appropriate notifications', async function () {
{
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken1, notifs)
}
{
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
await checkNotifications(servers[0], userToken2, notifs)
}
})
it('Should block an account', async function () {
await servers[0].blocklist.addToServerBlocklist({ server: servers[1].host })
await waitJobs(servers)
})
it('Should not have notifications from this account', async function () {
await checkNotifications(servers[0], userToken1, [])
await checkNotifications(servers[0], userToken2, [])
})
})
after(async function () {
await cleanupTests(servers)
})
})
+902
ファイルの表示
@@ -0,0 +1,902 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { UserNotificationType } from '@peertube/peertube-models'
import {
BlocklistCommand,
cleanupTests,
CommentsCommand,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
setDefaultAccountAvatar,
waitJobs
} from '@peertube/peertube-server-commands'
async function checkAllVideos (server: PeerTubeServer, token: string) {
{
const { data } = await server.videos.listWithToken({ token })
expect(data).to.have.lengthOf(5)
}
{
const { data } = await server.videos.list()
expect(data).to.have.lengthOf(5)
}
}
async function checkAllComments (server: PeerTubeServer, token: string, videoUUID: string) {
const { data } = await server.comments.listThreads({ videoId: videoUUID, start: 0, count: 25, sort: '-createdAt', token })
const threads = data.filter(t => t.isDeleted === false)
expect(threads).to.have.lengthOf(2)
for (const thread of threads) {
const tree = await server.comments.getThread({ videoId: videoUUID, threadId: thread.id, token })
expect(tree.children).to.have.lengthOf(1)
}
}
async function checkCommentNotification (
mainServer: PeerTubeServer,
comment: { server: PeerTubeServer, token: string, videoUUID: string, text: string },
check: 'presence' | 'absence'
) {
const command = comment.server.comments
const { threadId, createdAt } = await command.createThread({ token: comment.token, videoId: comment.videoUUID, text: comment.text })
await waitJobs([ mainServer, comment.server ])
const { data } = await mainServer.notifications.list({ start: 0, count: 30 })
const commentNotifications = data.filter(n => n.comment && n.comment.video.uuid === comment.videoUUID && n.createdAt >= createdAt)
if (check === 'presence') expect(commentNotifications).to.have.lengthOf(1)
else expect(commentNotifications).to.have.lengthOf(0)
await command.delete({ token: comment.token, videoId: comment.videoUUID, commentId: threadId })
await waitJobs([ mainServer, comment.server ])
}
describe('Test blocklist', function () {
let servers: PeerTubeServer[]
let videoUUID1: string
let videoUUID2: string
let videoUUID3: string
let userToken1: string
let userModeratorToken: string
let userToken2: string
let command: BlocklistCommand
let commentsCommand: CommentsCommand[]
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(3)
await setAccessTokensToServers(servers)
await setDefaultAccountAvatar(servers)
command = servers[0].blocklist
commentsCommand = servers.map(s => s.comments)
{
const user = { username: 'user1', password: 'password' }
await servers[0].users.create({ username: user.username, password: user.password })
userToken1 = await servers[0].login.getAccessToken(user)
await servers[0].videos.upload({ token: userToken1, attributes: { name: 'video user 1' } })
}
{
const user = { username: 'moderator', password: 'password' }
await servers[0].users.create({ username: user.username, password: user.password })
userModeratorToken = await servers[0].login.getAccessToken(user)
}
{
const user = { username: 'user2', password: 'password' }
await servers[1].users.create({ username: user.username, password: user.password })
userToken2 = await servers[1].login.getAccessToken(user)
await servers[1].videos.upload({ token: userToken2, attributes: { name: 'video user 2' } })
}
{
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video server 1' } })
videoUUID1 = uuid
}
{
const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video server 2' } })
videoUUID2 = uuid
}
{
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 2 server 1' } })
videoUUID3 = uuid
}
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
{
const created = await commentsCommand[0].createThread({ videoId: videoUUID1, text: 'comment root 1' })
const reply = await commentsCommand[0].addReply({
token: userToken1,
videoId: videoUUID1,
toCommentId: created.id,
text: 'comment user 1'
})
await commentsCommand[0].addReply({ videoId: videoUUID1, toCommentId: reply.id, text: 'comment root 1' })
}
{
const created = await commentsCommand[0].createThread({ token: userToken1, videoId: videoUUID1, text: 'comment user 1' })
await commentsCommand[0].addReply({ videoId: videoUUID1, toCommentId: created.id, text: 'comment root 1' })
}
await waitJobs(servers)
})
describe('User blocklist', function () {
describe('When managing account blocklist', function () {
it('Should list all videos', function () {
return checkAllVideos(servers[0], servers[0].accessToken)
})
it('Should list the comments', function () {
return checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
})
it('Should block a remote account', async function () {
await command.addToMyBlocklist({ account: 'user2@' + servers[1].host })
})
it('Should hide its videos', async function () {
const { data } = await servers[0].videos.listWithToken()
expect(data).to.have.lengthOf(4)
const v = data.find(v => v.name === 'video user 2')
expect(v).to.be.undefined
})
it('Should block a local account', async function () {
await command.addToMyBlocklist({ account: 'user1' })
})
it('Should hide its videos', async function () {
const { data } = await servers[0].videos.listWithToken()
expect(data).to.have.lengthOf(3)
const v = data.find(v => v.name === 'video user 1')
expect(v).to.be.undefined
})
it('Should hide its comments', async function () {
const { data } = await commentsCommand[0].listThreads({
token: servers[0].accessToken,
videoId: videoUUID1,
start: 0,
count: 25,
sort: '-createdAt'
})
expect(data).to.have.lengthOf(1)
expect(data[0].totalReplies).to.equal(1)
const t = data.find(t => t.text === 'comment user 1')
expect(t).to.be.undefined
for (const thread of data) {
const tree = await commentsCommand[0].getThread({
videoId: videoUUID1,
threadId: thread.id,
token: servers[0].accessToken
})
expect(tree.children).to.have.lengthOf(0)
}
})
it('Should not have notifications from blocked accounts', async function () {
this.timeout(20000)
{
const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
await checkCommentNotification(servers[0], comment, 'absence')
}
{
const comment = {
server: servers[0],
token: userToken1,
videoUUID: videoUUID2,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'absence')
}
})
it('Should list all the videos with another user', async function () {
return checkAllVideos(servers[0], userToken1)
})
it('Should list blocked accounts', async function () {
{
const body = await command.listMyAccountBlocklist({ start: 0, count: 1, sort: 'createdAt' })
expect(body.total).to.equal(2)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
expect(block.blockedAccount.displayName).to.equal('user2')
expect(block.blockedAccount.name).to.equal('user2')
expect(block.blockedAccount.host).to.equal('' + servers[1].host)
}
{
const body = await command.listMyAccountBlocklist({ start: 1, count: 2, sort: 'createdAt' })
expect(body.total).to.equal(2)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
expect(block.blockedAccount.displayName).to.equal('user1')
expect(block.blockedAccount.name).to.equal('user1')
expect(block.blockedAccount.host).to.equal('' + servers[0].host)
}
})
it('Should search blocked accounts', async function () {
const body = await command.listMyAccountBlocklist({ start: 0, count: 10, search: 'user2' })
expect(body.total).to.equal(1)
expect(body.data[0].blockedAccount.name).to.equal('user2')
})
it('Should get blocked status', async function () {
const remoteHandle = 'user2@' + servers[1].host
const localHandle = 'user1@' + servers[0].host
const unknownHandle = 'user5@' + servers[0].host
{
const status = await command.getStatus({ accounts: [ remoteHandle ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(1)
expect(status.accounts[remoteHandle].blockedByUser).to.be.false
expect(status.accounts[remoteHandle].blockedByServer).to.be.false
expect(Object.keys(status.hosts)).to.have.lengthOf(0)
}
{
const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ remoteHandle ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(1)
expect(status.accounts[remoteHandle].blockedByUser).to.be.true
expect(status.accounts[remoteHandle].blockedByServer).to.be.false
expect(Object.keys(status.hosts)).to.have.lengthOf(0)
}
{
const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ localHandle, remoteHandle, unknownHandle ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(3)
for (const handle of [ localHandle, remoteHandle ]) {
expect(status.accounts[handle].blockedByUser).to.be.true
expect(status.accounts[handle].blockedByServer).to.be.false
}
expect(status.accounts[unknownHandle].blockedByUser).to.be.false
expect(status.accounts[unknownHandle].blockedByServer).to.be.false
expect(Object.keys(status.hosts)).to.have.lengthOf(0)
}
})
it('Should not allow a remote blocked user to comment my videos', async function () {
this.timeout(60000)
{
await commentsCommand[1].createThread({ token: userToken2, videoId: videoUUID3, text: 'comment user 2' })
await waitJobs(servers)
await commentsCommand[0].createThread({ token: servers[0].accessToken, videoId: videoUUID3, text: 'uploader' })
await waitJobs(servers)
const commentId = await commentsCommand[1].findCommentId({ videoId: videoUUID3, text: 'uploader' })
const message = 'reply by user 2'
const reply = await commentsCommand[1].addReply({ token: userToken2, videoId: videoUUID3, toCommentId: commentId, text: message })
await commentsCommand[1].addReply({ videoId: videoUUID3, toCommentId: reply.id, text: 'another reply' })
await waitJobs(servers)
}
// Server 2 has all the comments
{
const { data } = await commentsCommand[1].listThreads({ videoId: videoUUID3, count: 25, sort: '-createdAt' })
expect(data).to.have.lengthOf(2)
expect(data[0].text).to.equal('uploader')
expect(data[1].text).to.equal('comment user 2')
const tree = await commentsCommand[1].getThread({ videoId: videoUUID3, threadId: data[0].id })
expect(tree.children).to.have.lengthOf(1)
expect(tree.children[0].comment.text).to.equal('reply by user 2')
expect(tree.children[0].children).to.have.lengthOf(1)
expect(tree.children[0].children[0].comment.text).to.equal('another reply')
}
// Server 1 and 3 should only have uploader comments
for (const server of [ servers[0], servers[2] ]) {
const { data } = await server.comments.listThreads({ videoId: videoUUID3, count: 25, sort: '-createdAt' })
expect(data).to.have.lengthOf(1)
expect(data[0].text).to.equal('uploader')
const tree = await server.comments.getThread({ videoId: videoUUID3, threadId: data[0].id })
if (server.serverNumber === 1) expect(tree.children).to.have.lengthOf(0)
else expect(tree.children).to.have.lengthOf(1)
}
})
it('Should unblock the remote account', async function () {
await command.removeFromMyBlocklist({ account: 'user2@' + servers[1].host })
})
it('Should display its videos', async function () {
const { data } = await servers[0].videos.listWithToken()
expect(data).to.have.lengthOf(4)
const v = data.find(v => v.name === 'video user 2')
expect(v).not.to.be.undefined
})
it('Should display its comments on my video', async function () {
for (const server of servers) {
const { data } = await server.comments.listThreads({ videoId: videoUUID3, count: 25, sort: '-createdAt' })
// Server 3 should not have 2 comment threads, because server 1 did not forward the server 2 comment
if (server.serverNumber === 3) {
expect(data).to.have.lengthOf(1)
continue
}
expect(data).to.have.lengthOf(2)
expect(data[0].text).to.equal('uploader')
expect(data[1].text).to.equal('comment user 2')
const tree = await server.comments.getThread({ videoId: videoUUID3, threadId: data[0].id })
expect(tree.children).to.have.lengthOf(1)
expect(tree.children[0].comment.text).to.equal('reply by user 2')
expect(tree.children[0].children).to.have.lengthOf(1)
expect(tree.children[0].children[0].comment.text).to.equal('another reply')
}
})
it('Should unblock the local account', async function () {
await command.removeFromMyBlocklist({ account: 'user1' })
})
it('Should display its comments', function () {
return checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
})
it('Should have a notification from a non blocked account', async function () {
this.timeout(20000)
{
const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
await checkCommentNotification(servers[0], comment, 'presence')
}
{
const comment = {
server: servers[0],
token: userToken1,
videoUUID: videoUUID2,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'presence')
}
})
})
describe('When managing server blocklist', function () {
it('Should list all videos', function () {
return checkAllVideos(servers[0], servers[0].accessToken)
})
it('Should list the comments', function () {
return checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
})
it('Should block a remote server', async function () {
await command.addToMyBlocklist({ server: '' + servers[1].host })
})
it('Should hide its videos', async function () {
const { data } = await servers[0].videos.listWithToken()
expect(data).to.have.lengthOf(3)
const v1 = data.find(v => v.name === 'video user 2')
const v2 = data.find(v => v.name === 'video server 2')
expect(v1).to.be.undefined
expect(v2).to.be.undefined
})
it('Should list all the videos with another user', async function () {
return checkAllVideos(servers[0], userToken1)
})
it('Should hide its comments', async function () {
const { id } = await commentsCommand[1].createThread({ token: userToken2, videoId: videoUUID1, text: 'hidden comment 2' })
await waitJobs(servers)
await checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
await commentsCommand[1].delete({ token: userToken2, videoId: videoUUID1, commentId: id })
})
it('Should not have notifications from blocked server', async function () {
this.timeout(20000)
{
const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
await checkCommentNotification(servers[0], comment, 'absence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'absence')
}
})
it('Should list blocked servers', async function () {
const body = await command.listMyServerBlocklist({ start: 0, count: 1, sort: 'createdAt' })
expect(body.total).to.equal(1)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
expect(block.blockedServer.host).to.equal('' + servers[1].host)
})
it('Should search blocked servers', async function () {
const body = await command.listMyServerBlocklist({ start: 0, count: 10, search: servers[1].host })
expect(body.total).to.equal(1)
expect(body.data[0].blockedServer.host).to.equal(servers[1].host)
})
it('Should get blocklist status', async function () {
const blockedServer = servers[1].host
const notBlockedServer = 'example.com'
{
const status = await command.getStatus({ hosts: [ blockedServer, notBlockedServer ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(0)
expect(Object.keys(status.hosts)).to.have.lengthOf(2)
expect(status.hosts[blockedServer].blockedByUser).to.be.false
expect(status.hosts[blockedServer].blockedByServer).to.be.false
expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
}
{
const status = await command.getStatus({ token: servers[0].accessToken, hosts: [ blockedServer, notBlockedServer ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(0)
expect(Object.keys(status.hosts)).to.have.lengthOf(2)
expect(status.hosts[blockedServer].blockedByUser).to.be.true
expect(status.hosts[blockedServer].blockedByServer).to.be.false
expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
}
})
it('Should unblock the remote server', async function () {
await command.removeFromMyBlocklist({ server: '' + servers[1].host })
})
it('Should display its videos', function () {
return checkAllVideos(servers[0], servers[0].accessToken)
})
it('Should display its comments', function () {
return checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
})
it('Should have notification from unblocked server', async function () {
this.timeout(20000)
{
const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
await checkCommentNotification(servers[0], comment, 'presence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'presence')
}
})
})
})
describe('Server blocklist', function () {
describe('When managing account blocklist', function () {
it('Should list all videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllVideos(servers[0], token)
}
})
it('Should list the comments', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllComments(servers[0], token, videoUUID1)
}
})
it('Should block a remote account', async function () {
await command.addToServerBlocklist({ account: 'user2@' + servers[1].host })
})
it('Should hide its videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
const { data } = await servers[0].videos.listWithToken({ token })
expect(data).to.have.lengthOf(4)
const v = data.find(v => v.name === 'video user 2')
expect(v).to.be.undefined
}
})
it('Should block a local account', async function () {
await command.addToServerBlocklist({ account: 'user1' })
})
it('Should hide its videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
const { data } = await servers[0].videos.listWithToken({ token })
expect(data).to.have.lengthOf(3)
const v = data.find(v => v.name === 'video user 1')
expect(v).to.be.undefined
}
})
it('Should hide its comments', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
const { data } = await commentsCommand[0].listThreads({ videoId: videoUUID1, count: 20, sort: '-createdAt', token })
const threads = data.filter(t => t.isDeleted === false)
expect(threads).to.have.lengthOf(1)
expect(threads[0].totalReplies).to.equal(1)
const t = threads.find(t => t.text === 'comment user 1')
expect(t).to.be.undefined
for (const thread of threads) {
const tree = await commentsCommand[0].getThread({ videoId: videoUUID1, threadId: thread.id, token })
expect(tree.children).to.have.lengthOf(0)
}
}
})
it('Should not have notification from blocked accounts by instance', async function () {
this.timeout(20000)
{
const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
await checkCommentNotification(servers[0], comment, 'absence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'absence')
}
})
it('Should list blocked accounts', async function () {
{
const body = await command.listServerAccountBlocklist({ start: 0, count: 1, sort: 'createdAt' })
expect(body.total).to.equal(2)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('peertube')
expect(block.byAccount.name).to.equal('peertube')
expect(block.blockedAccount.displayName).to.equal('user2')
expect(block.blockedAccount.name).to.equal('user2')
expect(block.blockedAccount.host).to.equal('' + servers[1].host)
}
{
const body = await command.listServerAccountBlocklist({ start: 1, count: 2, sort: 'createdAt' })
expect(body.total).to.equal(2)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('peertube')
expect(block.byAccount.name).to.equal('peertube')
expect(block.blockedAccount.displayName).to.equal('user1')
expect(block.blockedAccount.name).to.equal('user1')
expect(block.blockedAccount.host).to.equal('' + servers[0].host)
}
})
it('Should search blocked accounts', async function () {
const body = await command.listServerAccountBlocklist({ start: 0, count: 10, search: 'user2' })
expect(body.total).to.equal(1)
expect(body.data[0].blockedAccount.name).to.equal('user2')
})
it('Should get blocked status', async function () {
const remoteHandle = 'user2@' + servers[1].host
const localHandle = 'user1@' + servers[0].host
const unknownHandle = 'user5@' + servers[0].host
for (const token of [ undefined, servers[0].accessToken ]) {
const status = await command.getStatus({ token, accounts: [ localHandle, remoteHandle, unknownHandle ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(3)
for (const handle of [ localHandle, remoteHandle ]) {
expect(status.accounts[handle].blockedByUser).to.be.false
expect(status.accounts[handle].blockedByServer).to.be.true
}
expect(status.accounts[unknownHandle].blockedByUser).to.be.false
expect(status.accounts[unknownHandle].blockedByServer).to.be.false
expect(Object.keys(status.hosts)).to.have.lengthOf(0)
}
})
it('Should unblock the remote account', async function () {
await command.removeFromServerBlocklist({ account: 'user2@' + servers[1].host })
})
it('Should display its videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
const { data } = await servers[0].videos.listWithToken({ token })
expect(data).to.have.lengthOf(4)
const v = data.find(v => v.name === 'video user 2')
expect(v).not.to.be.undefined
}
})
it('Should unblock the local account', async function () {
await command.removeFromServerBlocklist({ account: 'user1' })
})
it('Should display its comments', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllComments(servers[0], token, videoUUID1)
}
})
it('Should have notifications from unblocked accounts', async function () {
this.timeout(20000)
{
const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' }
await checkCommentNotification(servers[0], comment, 'presence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'presence')
}
})
})
describe('When managing server blocklist', function () {
it('Should list all videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllVideos(servers[0], token)
}
})
it('Should list the comments', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllComments(servers[0], token, videoUUID1)
}
})
it('Should block a remote server', async function () {
await command.addToServerBlocklist({ server: '' + servers[1].host })
})
it('Should hide its videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
const requests = [
servers[0].videos.list(),
servers[0].videos.listWithToken({ token })
]
for (const req of requests) {
const { data } = await req
expect(data).to.have.lengthOf(3)
const v1 = data.find(v => v.name === 'video user 2')
const v2 = data.find(v => v.name === 'video server 2')
expect(v1).to.be.undefined
expect(v2).to.be.undefined
}
}
})
it('Should hide its comments', async function () {
const { id } = await commentsCommand[1].createThread({ token: userToken2, videoId: videoUUID1, text: 'hidden comment 2' })
await waitJobs(servers)
await checkAllComments(servers[0], servers[0].accessToken, videoUUID1)
await commentsCommand[1].delete({ token: userToken2, videoId: videoUUID1, commentId: id })
})
it('Should not have notification from blocked instances by instance', async function () {
this.timeout(50000)
{
const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
await checkCommentNotification(servers[0], comment, 'absence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'absence')
}
{
const now = new Date()
await servers[1].follows.unfollow({ target: servers[0] })
await waitJobs(servers)
await servers[1].follows.follow({ hosts: [ servers[0].host ] })
await waitJobs(servers)
const { data } = await servers[0].notifications.list({ start: 0, count: 30 })
const commentNotifications = data.filter(n => {
return n.type === UserNotificationType.NEW_INSTANCE_FOLLOWER && n.createdAt >= now.toISOString()
})
expect(commentNotifications).to.have.lengthOf(0)
}
})
it('Should list blocked servers', async function () {
const body = await command.listServerServerBlocklist({ start: 0, count: 1, sort: 'createdAt' })
expect(body.total).to.equal(1)
const block = body.data[0]
expect(block.byAccount.displayName).to.equal('peertube')
expect(block.byAccount.name).to.equal('peertube')
expect(block.blockedServer.host).to.equal('' + servers[1].host)
})
it('Should search blocked servers', async function () {
const body = await command.listServerServerBlocklist({ start: 0, count: 10, search: servers[1].host })
expect(body.total).to.equal(1)
expect(body.data[0].blockedServer.host).to.equal(servers[1].host)
})
it('Should get blocklist status', async function () {
const blockedServer = servers[1].host
const notBlockedServer = 'example.com'
for (const token of [ undefined, servers[0].accessToken ]) {
const status = await command.getStatus({ token, hosts: [ blockedServer, notBlockedServer ] })
expect(Object.keys(status.accounts)).to.have.lengthOf(0)
expect(Object.keys(status.hosts)).to.have.lengthOf(2)
expect(status.hosts[blockedServer].blockedByUser).to.be.false
expect(status.hosts[blockedServer].blockedByServer).to.be.true
expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
}
})
it('Should unblock the remote server', async function () {
await command.removeFromServerBlocklist({ server: '' + servers[1].host })
})
it('Should list all videos', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllVideos(servers[0], token)
}
})
it('Should list the comments', async function () {
for (const token of [ userModeratorToken, servers[0].accessToken ]) {
await checkAllComments(servers[0], token, videoUUID1)
}
})
it('Should have notification from unblocked instances', async function () {
this.timeout(50000)
{
const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
await checkCommentNotification(servers[0], comment, 'presence')
}
{
const comment = {
server: servers[1],
token: userToken2,
videoUUID: videoUUID1,
text: 'hello @root@' + servers[0].host
}
await checkCommentNotification(servers[0], comment, 'presence')
}
{
const now = new Date()
await servers[1].follows.unfollow({ target: servers[0] })
await waitJobs(servers)
await servers[1].follows.follow({ hosts: [ servers[0].host ] })
await waitJobs(servers)
const { data } = await servers[0].notifications.list({ start: 0, count: 30 })
const commentNotifications = data.filter(n => {
return n.type === UserNotificationType.NEW_INSTANCE_FOLLOWER && n.createdAt >= now.toISOString()
})
expect(commentNotifications).to.have.lengthOf(1)
}
})
})
})
after(async function () {
await cleanupTests(servers)
})
})
+552
ファイルの表示
@@ -0,0 +1,552 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
ActivityApproveReply,
ActivityPubOrderedCollection,
HttpStatusCode,
UserRole,
VideoCommentObject,
VideoCommentPolicy,
VideoCommentPolicyType,
VideoPrivacy
} from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests, createMultipleServers,
doubleFollow,
makeActivityPubGetRequest, makeActivityPubRawRequest, setAccessTokensToServers,
setDefaultAccountAvatar,
waitJobs
} from '@peertube/peertube-server-commands'
import { expectStartWith } from '@tests/shared/checks.js'
import { expect } from 'chai'
describe('Test comments approval', function () {
let servers: PeerTubeServer[]
let userToken: string
let anotherUserToken: string
let moderatorToken: string
async function createVideo (commentsPolicy: VideoCommentPolicyType) {
const { uuid } = await servers[0].videos.upload({
token: userToken,
attributes: {
name: 'review policy: ' + commentsPolicy,
privacy: VideoPrivacy.PUBLIC,
commentsPolicy
}
})
await waitJobs(servers)
return uuid
}
before(async function () {
this.timeout(120000)
servers = await createMultipleServers(3)
await setAccessTokensToServers(servers)
await setDefaultAccountAvatar(servers)
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
await doubleFollow(servers[1], servers[2])
userToken = await servers[0].users.generateUserAndToken('user1')
anotherUserToken = await servers[0].users.generateUserAndToken('user2')
moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
})
describe('On video with comments requiring approval', function () {
let videoId: string
before(async function () {
this.timeout(30000)
videoId = await createVideo(VideoCommentPolicy.REQUIRES_APPROVAL)
})
it('Should create a local and remote comment that require approval', async function () {
this.timeout(30000)
await servers[0].comments.createThread({ text: 'local', videoId, token: anotherUserToken })
await servers[1].comments.createThread({ text: 'remote', videoId })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
expect(data).to.have.lengthOf(2)
for (const c of data) {
expect(c.heldForReview).to.be.true
}
})
it('Should display comments depending on the user', async function () {
// Owner see the comments
{
const { data } = await servers[0].comments.listThreads({ videoId, token: userToken })
expect(data).to.have.lengthOf(2)
for (const c of data) {
expect(c.heldForReview).to.be.true
}
}
// Anonymous doesn't see the comments
for (const server of servers) {
const { data } = await server.comments.listThreads({ videoId })
expect(data).to.have.lengthOf(0)
}
// Owner of the comment can see it
{
const { data } = await servers[1].comments.listThreads({ videoId, token: servers[1].accessToken })
expect(data).to.have.lengthOf(1)
expect(data[0].heldForReview).to.be.true
expect(data[0].text).to.equal('remote')
}
})
it('Should create a local and remote reply and require approval', async function () {
await servers[0].comments.addReplyToLastThread({ text: 'local reply', token: anotherUserToken })
await servers[1].comments.addReplyToLastThread({ text: 'remote reply' })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
expect(data).to.have.lengthOf(4)
for (const c of data) {
expect(c.heldForReview).to.be.true
}
})
it('Should approve a thread comment', async function () {
{
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
const commentId = data.find(c => c.text === 'remote').id
await servers[0].comments.approve({ commentId, videoId, token: userToken })
await waitJobs(servers)
}
// Owner and moderators
for (const token of [ userToken, moderatorToken ]) {
const { data: threads } = await servers[0].comments.listThreads({ videoId, token })
expect(threads).to.have.lengthOf(2)
for (const c of threads) {
if (c.text === 'remote') expect(c.heldForReview).to.be.false
else expect(c.heldForReview).to.be.true
const thread = await servers[0].comments.getThread({ videoId, threadId: c.id, token })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.heldForReview).to.equal(true)
}
}
// Anonymous
for (const server of servers) {
const { data } = await server.comments.listThreads({ videoId })
expect(data).to.have.lengthOf(1)
expect(data[0].heldForReview).to.be.false
expect(data[0].text).to.equal('remote')
const thread = await server.comments.getThread({ videoId, threadId: data[0].id })
expect(thread.children).to.have.lengthOf(0)
}
// Owner of the comment can see it
{
const { data } = await servers[1].comments.listThreads({ videoId, token: servers[1].accessToken })
expect(data).to.have.lengthOf(1)
expect(data[0].heldForReview).to.be.false
expect(data[0].text).to.equal('remote')
const thread = await servers[1].comments.getThread({ videoId, threadId: data[0].id, token: servers[1].accessToken })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.heldForReview).to.equal(true)
}
})
it('Should approve a reply comment', async function () {
{
const commentId = await servers[0].comments.findCommentId({ videoId, text: 'remote reply' })
await servers[0].comments.approve({ commentId, videoId, token: userToken })
await waitJobs(servers)
// Owner
{
const { data } = await servers[0].comments.listThreads({ videoId, token: userToken })
expect(data.filter(c => c.heldForReview)).to.have.lengthOf(1)
const thread = await servers[0].comments.getThreadOf({ videoId, text: 'remote', token: userToken })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.text).to.equal('remote reply')
expect(thread.children[0].comment.heldForReview).to.be.false
}
// Other users
for (const server of servers) {
const thread = await server.comments.getThreadOf({ videoId, text: 'remote' })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.text).to.equal('remote reply')
expect(thread.children[0].comment.heldForReview).to.be.false
}
}
})
it('Should list and filter on comments awaiting approval', async function () {
{
const { total, data } = await servers[0].comments.listCommentsOnMyVideos({ videoId, token: userToken })
expect(total).to.equal(4)
expect(data).to.have.lengthOf(4)
}
{
const { total, data } = await servers[0].comments.listCommentsOnMyVideos({ videoId, token: userToken, isHeldForReview: true })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
expect(data.filter(c => c.heldForReview)).to.have.lengthOf(2)
}
{
const { total, data } = await servers[0].comments.listCommentsOnMyVideos({ videoId, token: userToken, isHeldForReview: false })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
expect(data.filter(c => !c.heldForReview)).to.have.lengthOf(2)
}
})
it('Should approve a reply of a non approved reply', async function () {
const threadId = await servers[0].comments.findCommentId({ videoId, text: 'local' })
const { id: replyId } = await servers[0].comments.addReply({
videoId,
toCommentId: await servers[0].comments.findCommentId({ videoId, text: 'local reply' }),
text: 'local reply 2',
token: anotherUserToken
})
await servers[0].comments.approve({ commentId: replyId, videoId, token: userToken })
await servers[0].comments.approve({ commentId: threadId, videoId, token: userToken })
await waitJobs(servers)
// Owner
{
const { data } = await servers[0].comments.listThreads({ videoId, token: userToken })
expect(data.filter(c => c.heldForReview)).to.have.lengthOf(0)
const thread = await servers[0].comments.getThreadOf({ videoId, text: 'local', token: userToken })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.text).to.equal('local reply')
expect(thread.children[0].comment.heldForReview).to.be.true
expect(thread.children[0].children).to.have.lengthOf(1)
expect(thread.children[0].children[0].comment.text).to.equal('local reply 2')
expect(thread.children[0].children[0].comment.heldForReview).to.be.false
}
// Other users
for (const server of servers) {
const thread = await server.comments.getThreadOf({ videoId, text: 'local' })
expect(thread.children).to.have.lengthOf(0)
}
})
it('Should have appropriate ActivityPub representation', async function () {
const localNonApprovedId = await servers[0].comments.findCommentId({ text: 'local reply', videoId })
const localApprovedId = await servers[0].comments.findCommentId({ text: 'local', videoId })
const remoteApprovedId = await servers[0].comments.findCommentId({ text: 'remote', videoId })
{
for (const page of [ 1, 2 ]) {
const res = await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${videoId}/comments?page=${page}`)
const { totalItems, orderedItems } = res.body as ActivityPubOrderedCollection<string>
expect(totalItems).to.equal(4)
expect(orderedItems.some(url => url === `${servers[0].url}/videos/watch/${videoId}/comments/${localNonApprovedId}`)).to.be.false
}
}
{
await makeActivityPubGetRequest(
servers[0].url,
`/videos/watch/${videoId}/comments/${localNonApprovedId}`,
HttpStatusCode.NOT_FOUND_404
)
await makeActivityPubGetRequest(
servers[0].url,
`/videos/watch/${videoId}/comments/${localNonApprovedId}/approve-reply`,
HttpStatusCode.NOT_FOUND_404
)
}
const toTest = [ { server: servers[0], commentId: localApprovedId }, { server: servers[1], commentId: remoteApprovedId } ]
for (const { server, commentId } of toTest) {
const res = await makeActivityPubGetRequest(server.url, `/videos/watch/${videoId}/comments/${commentId}`)
const { replyApproval } = res.body as VideoCommentObject
expectStartWith(replyApproval, `${servers[0].url}/videos/watch/${videoId}/comments/`)
const res2 = await makeActivityPubRawRequest(replyApproval, HttpStatusCode.OK_200)
const object = res2.body as ActivityApproveReply
expect(object.type).to.equal('ApproveReply')
}
})
it('Should remove an approved/non-approved comments', async function () {
this.timeout(60000)
{
const commentId = await servers[1].comments.findCommentId({ videoId, text: 'local' })
await servers[1].comments.addReply({ videoId, toCommentId: commentId, text: 'remote reply on local' })
await waitJobs(servers)
}
for (const text of [ 'remote', 'local reply', 'remote reply on local' ]) {
const commentId = await servers[0].comments.findCommentId({ videoId, text })
await servers[0].comments.delete({ videoId, commentId })
}
await waitJobs(servers)
// Owner
{
const { data } = await servers[0].comments.listThreads({ videoId, token: userToken, sort: '-createdAt' })
expect(data).to.have.lengthOf(2)
{
const remote = data[0]
expect(remote.isDeleted).to.be.true
const thread = await servers[0].comments.getThread({ videoId, token: userToken, threadId: remote.id })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.text).to.equal('remote reply')
}
{
const local = data[1]
expect(local.isDeleted).to.be.false
const thread = await servers[0].comments.getThread({ videoId, token: userToken, threadId: local.id })
expect(thread.children).to.have.lengthOf(2)
{
const localReply = thread.children[0]
expect(localReply.comment.deletedAt).to.exist
expect(localReply.comment.heldForReview).to.be.true
expect(localReply.children).to.have.lengthOf(1)
expect(localReply.children).to.have.lengthOf(1)
expect(localReply.children[0].comment.text).to.equal('local reply 2')
expect(localReply.children[0].comment.heldForReview).to.be.false
expect(localReply.children[0].children).to.have.lengthOf(0)
}
{
expect(thread.children[1].comment.deletedAt).to.exist
}
}
}
// Other users
for (const server of servers) {
const { data } = await server.comments.listThreads({ videoId, sort: '-createdAt' })
expect(data).to.have.lengthOf(2)
{
const remote = data[0]
expect(remote.isDeleted).to.be.true
const thread = await server.comments.getThread({ videoId, threadId: remote.id })
expect(thread.children).to.have.lengthOf(1)
expect(thread.children[0].comment.text).to.equal('remote reply')
}
{
const local = data[1]
expect(local.isDeleted).to.be.false
const thread = await server.comments.getThread({ videoId, threadId: local.id })
// Anonymous users cannot see the thread because the delete comment was held for review
expect(thread.children).to.have.lengthOf(0)
}
}
})
it('Should not require review for video uploader, admins and moderators', async function () {
for (const token of [ userToken, moderatorToken, servers[0].accessToken ]) {
await servers[0].comments.createThread({ videoId, text: 'right', token })
}
await waitJobs(servers)
for (const server of servers) {
const { data } = await server.comments.listThreads({ videoId, sort: '-createdAt' })
expect(data.filter(c => c.text === 'right')).to.have.lengthOf(3)
}
})
})
describe('On video with comments with some tags requiring approval', function () {
let videoId: string
before(async function () {
this.timeout(30000)
videoId = await createVideo(VideoCommentPolicy.ENABLED)
})
it('Should only have built-in auto tag policies and no policies set', async function () {
const { review } = await servers[0].autoTags.getCommentPolicies({ accountName: 'user1', token: userToken })
expect(review).to.have.lengthOf(0)
const { available } = await servers[0].autoTags.getAccountAvailable({ accountName: 'user1', token: userToken })
expect(available.map(a => a.name)).to.deep.equal([ 'external-link' ])
})
it('Should add watched words and so available tag policies', async function () {
await servers[0].watchedWordsLists.createList({
token: userToken,
listName: 'forbidden-list',
words: [ 'forbidden' ],
accountName: 'user1'
})
await servers[0].watchedWordsLists.createList({
token: userToken,
listName: 'allowed-list',
words: [ 'allowed' ],
accountName: 'user1'
})
const { review } = await servers[0].autoTags.getCommentPolicies({ accountName: 'user1', token: userToken })
expect(review).to.have.lengthOf(0)
const { available } = await servers[0].autoTags.getAccountAvailable({ accountName: 'user1', token: userToken })
expect(available.map(a => a.name)).to.have.deep.members([ 'external-link', 'forbidden-list', 'allowed-list' ])
})
it('Should update policies', async function () {
await servers[0].autoTags.updateCommentPolicies({
accountName: 'user1',
review: [ 'external-link', 'forbidden-list' ],
token: userToken
})
const { review } = await servers[0].autoTags.getCommentPolicies({ accountName: 'user1', token: userToken })
expect(review).to.have.deep.members([ 'external-link', 'forbidden-list' ])
})
it('Should publish a comment without approval', async function () {
const threadText = '1 - framasoft and allowed'
const replyText = '1 - frama and allowed'
await servers[0].comments.createThread({ token: anotherUserToken, videoId, text: threadText })
await waitJobs(servers)
const commentId = await servers[1].comments.findCommentId({ videoId, text: threadText })
await servers[1].comments.addReply({ text: replyText, videoId, toCommentId: commentId })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
const t = data.find(c => c.text === threadText)
const r = data.find(c => c.text === replyText)
expect(t.automaticTags).to.have.members([ 'allowed-list' ])
expect(t.heldForReview).to.be.false
expect(r.automaticTags).to.have.members([ 'allowed-list' ])
expect(r.heldForReview).to.be.false
})
it('Should publish a comment with approval', async function () {
const threadText = '2 - framasoft.org and allowed'
const replyText = '2 - https://framasoft.org and forbidden'
await servers[1].comments.createThread({ videoId, text: threadText })
await waitJobs(servers)
const commentId = await servers[0].comments.findCommentId({ videoId, text: threadText })
await servers[0].comments.addReply({ token: anotherUserToken, text: replyText, videoId, toCommentId: commentId })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
const t = data.find(c => c.text === threadText)
const r = data.find(c => c.text === replyText)
expect(t.automaticTags).to.have.members([ 'external-link', 'allowed-list' ])
expect(t.heldForReview).to.be.true
expect(r.automaticTags).to.have.members([ 'external-link', 'forbidden-list' ])
expect(r.heldForReview).to.be.true
})
it('Should update policies and not update previously tags set', async function () {
await servers[0].autoTags.updateCommentPolicies({ accountName: 'user1', review: [ 'forbidden-list' ], token: userToken })
const { review } = await servers[0].autoTags.getCommentPolicies({ accountName: 'user1', token: userToken })
expect(review).to.have.deep.members([ 'forbidden-list' ])
const { available } = await servers[0].autoTags.getAccountAvailable({ accountName: 'user1', token: userToken })
expect(available.map(a => a.name)).to.have.deep.members([ 'external-link', 'forbidden-list', 'allowed-list' ])
const { data } = await servers[0].comments.listCommentsOnMyVideos({ videoId, token: userToken })
expect(data.filter(c => c.heldForReview)).to.have.lengthOf(2)
})
it('Should publish a comment with and without approval base on the new policies', async function () {
const threadText = '3 - framasoft.org and allowed'
const replyText = '3 - forbidden'
await servers[0].comments.createThread({ token: anotherUserToken, videoId, text: threadText })
await servers[0].comments.addReplyToLastThread({ token: anotherUserToken, text: replyText })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
const t = data.find(c => c.text === threadText)
const r = data.find(c => c.text === replyText)
expect(t.automaticTags).to.have.members([ 'external-link', 'allowed-list' ])
expect(t.heldForReview).to.be.false
expect(r.automaticTags).to.have.members([ 'forbidden-list' ])
expect(r.heldForReview).to.be.true
})
it('Should not require approval for a moderator but it should have the tag set', async function () {
await servers[0].comments.createThread({ token: moderatorToken, videoId, text: 'forbidden' })
await waitJobs(servers)
const { data } = await servers[0].comments.listCommentsOnMyVideos({ token: userToken })
const t = data.find(c => c.text === 'forbidden')
expect(t.automaticTags).to.have.members([ 'forbidden-list' ])
expect(t.heldForReview).to.be.false
})
it('Should not have threads waiting for approval before approbation for anonymous users on server 1 and 3', async function () {
for (const server of [ servers[0], servers[2] ]) {
const { data } = await server.comments.listThreads({ videoId })
expect(data).to.have.lengthOf(3)
expect(data.some(c => c.text === '2 - framasoft.org and allowed')).to.be.false
}
})
it('Should see threads waiting for approval before approbation for anonymous users on server 2', async function () {
const { data } = await servers[1].comments.listThreads({ videoId })
expect(data).to.have.lengthOf(4)
expect(data.some(c => c.text === '2 - framasoft.org and allowed')).to.be.true
expect(data.some(c => c.text === '2 - https://framasoft.org and forbidden')).to.be.false
})
})
after(async function () {
await cleanupTests(servers)
})
})
+6
ファイルの表示
@@ -0,0 +1,6 @@
export * from './abuses.js'
export * from './automatic-tags.js'
export * from './blocklist-notification.js'
export * from './blocklist.js'
export * from './video-blacklist.js'
export * from './watched-words.js'
+408
ファイルの表示
@@ -0,0 +1,408 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { sortObjectComparator } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
import {
BlacklistCommand,
cleanupTests,
createMultipleServers,
doubleFollow, makeActivityPubGetRequest, PeerTubeServer,
setAccessTokensToServers,
setDefaultChannelAvatar,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video blacklist', function () {
let servers: PeerTubeServer[] = []
let videoId: number
let command: BlacklistCommand
async function blacklistVideosOnServer (server: PeerTubeServer) {
const { data } = await server.videos.list()
for (const video of data) {
await server.blacklist.add({ videoId: video.id, reason: 'super reason' })
}
}
before(async function () {
this.timeout(120000)
// Run servers
servers = await createMultipleServers(2)
// Get the access tokens
await setAccessTokensToServers(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
await setDefaultChannelAvatar(servers[0])
// Upload 2 videos on server 2
await servers[1].videos.upload({ attributes: { name: 'My 1st video', description: 'A video on server 2' } })
await servers[1].videos.upload({ attributes: { name: 'My 2nd video', description: 'A video on server 2' } })
// Wait videos propagation, server 2 has transcoding enabled
await waitJobs(servers)
command = servers[0].blacklist
// Blacklist the two videos on server 1
await blacklistVideosOnServer(servers[0])
})
describe('When listing/searching videos', function () {
it('Should not have the video blacklisted in videos list/search on server 1', async function () {
{
const { total, data } = await servers[0].videos.list()
expect(total).to.equal(0)
expect(data).to.be.an('array')
expect(data.length).to.equal(0)
}
{
const body = await servers[0].search.searchVideos({ search: 'video' })
expect(body.total).to.equal(0)
expect(body.data).to.be.an('array')
expect(body.data.length).to.equal(0)
}
})
it('Should have the blacklisted video in videos list/search on server 2', async function () {
{
const { total, data } = await servers[1].videos.list()
expect(total).to.equal(2)
expect(data).to.be.an('array')
expect(data.length).to.equal(2)
}
{
const body = await servers[1].search.searchVideos({ search: 'video' })
expect(body.total).to.equal(2)
expect(body.data).to.be.an('array')
expect(body.data.length).to.equal(2)
}
})
})
describe('When listing manually blacklisted videos', function () {
it('Should display all the blacklisted videos', async function () {
const body = await command.list()
expect(body.total).to.equal(2)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(2)
for (const blacklistedVideo of blacklistedVideos) {
expect(blacklistedVideo.reason).to.equal('super reason')
videoId = blacklistedVideo.video.id
}
})
it('Should display all the blacklisted videos when applying manual type filter', async function () {
const body = await command.list({ type: VideoBlacklistType.MANUAL })
expect(body.total).to.equal(2)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(2)
})
it('Should display nothing when applying automatic type filter', async function () {
const body = await command.list({ type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
expect(body.total).to.equal(0)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(0)
})
it('Should get the correct sort when sorting by descending id', async function () {
const body = await command.list({ sort: '-id' })
expect(body.total).to.equal(2)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(2)
const result = [ ...body.data ].sort(sortObjectComparator('id', 'desc'))
expect(blacklistedVideos).to.deep.equal(result)
})
it('Should get the correct sort when sorting by descending video name', async function () {
const body = await command.list({ sort: '-name' })
expect(body.total).to.equal(2)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(2)
const result = [ ...body.data ].sort(sortObjectComparator('name', 'desc'))
expect(blacklistedVideos).to.deep.equal(result)
})
it('Should get the correct sort when sorting by ascending creation date', async function () {
const body = await command.list({ sort: 'createdAt' })
expect(body.total).to.equal(2)
const blacklistedVideos = body.data
expect(blacklistedVideos).to.be.an('array')
expect(blacklistedVideos.length).to.equal(2)
const result = [ ...body.data ].sort(sortObjectComparator('createdAt', 'asc'))
expect(blacklistedVideos).to.deep.equal(result)
})
})
describe('When updating blacklisted videos', function () {
it('Should change the reason', async function () {
await command.update({ videoId, reason: 'my super reason updated' })
const body = await command.list({ sort: '-name' })
const video = body.data.find(b => b.video.id === videoId)
expect(video.reason).to.equal('my super reason updated')
})
})
describe('When listing my videos', function () {
it('Should display blacklisted videos', async function () {
await blacklistVideosOnServer(servers[1])
const { total, data } = await servers[1].videos.listMyVideos()
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
for (const video of data) {
expect(video.blacklisted).to.be.true
expect(video.blacklistedReason).to.equal('super reason')
}
})
})
describe('When removing a blacklisted video', function () {
let videoToRemove: VideoBlacklist
let blacklist = []
it('Should not have any video in videos list on server 1', async function () {
const { total, data } = await servers[0].videos.list()
expect(total).to.equal(0)
expect(data).to.be.an('array')
expect(data.length).to.equal(0)
})
it('Should remove a video from the blacklist on server 1', async function () {
// Get one video in the blacklist
const body = await command.list({ sort: '-name' })
videoToRemove = body.data[0]
blacklist = body.data.slice(1)
// Remove it
await command.remove({ videoId: videoToRemove.video.id })
})
it('Should have the ex-blacklisted video in videos list on server 1', async function () {
const { total, data } = await servers[0].videos.list()
expect(total).to.equal(1)
expect(data).to.be.an('array')
expect(data.length).to.equal(1)
expect(data[0].name).to.equal(videoToRemove.video.name)
expect(data[0].id).to.equal(videoToRemove.video.id)
})
it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
const body = await command.list({ sort: '-name' })
expect(body.total).to.equal(1)
const videos = body.data
expect(videos).to.be.an('array')
expect(videos.length).to.equal(1)
expect(videos).to.deep.equal(blacklist)
})
})
describe('When blacklisting local videos', function () {
let video3UUID: string
let video4UUID: string
before(async function () {
{
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'Video 3' } })
video3UUID = uuid
}
{
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'Video 4' } })
video4UUID = uuid
}
await waitJobs(servers)
})
it('Should blacklist video 3 and keep it federated', async function () {
await command.add({ videoId: video3UUID, reason: 'super reason', unfederate: false })
await waitJobs(servers)
{
const { data } = await servers[0].videos.list()
expect(data.find(v => v.uuid === video3UUID)).to.be.undefined
}
{
const { data } = await servers[1].videos.list()
expect(data.find(v => v.uuid === video3UUID)).to.not.be.undefined
}
})
it('Should unfederate the video', async function () {
await command.add({ videoId: video4UUID, reason: 'super reason', unfederate: true })
await waitJobs(servers)
for (const server of servers) {
const { data } = await server.videos.list()
expect(data.find(v => v.uuid === video4UUID)).to.be.undefined
}
})
it('Should have the video unfederated even after an Update AP message', async function () {
await servers[0].videos.update({ id: video4UUID, attributes: { description: 'super description' } })
await waitJobs(servers)
for (const server of servers) {
const { data } = await server.videos.list()
expect(data.find(v => v.uuid === video4UUID)).to.be.undefined
}
})
it('Should have the correct video blacklist unfederate attribute', async function () {
const body = await command.list({ sort: 'createdAt' })
const blacklistedVideos = body.data
const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID)
const video4Blacklisted = blacklistedVideos.find(b => b.video.uuid === video4UUID)
expect(video3Blacklisted.unfederated).to.be.false
expect(video4Blacklisted.unfederated).to.be.true
})
it('Should not have AP comments/announces/likes/dislikes', async function () {
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/comments`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/announces`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/likes`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/dislikes`, HttpStatusCode.UNAUTHORIZED_401)
})
it('Should remove the video from blacklist and refederate the video', async function () {
await command.remove({ videoId: video4UUID })
await waitJobs(servers)
for (const server of servers) {
const { data } = await server.videos.list()
expect(data.find(v => v.uuid === video4UUID)).to.not.be.undefined
}
})
})
describe('When auto blacklist videos', function () {
let userWithoutFlag: string
let userWithFlag: string
let channelOfUserWithoutFlag: number
before(async function () {
this.timeout(20000)
await servers[0].config.enableAutoBlacklist()
{
const user = { username: 'user_without_flag', password: 'password' }
await servers[0].users.create({
username: user.username,
adminFlags: UserAdminFlag.NONE,
password: user.password,
role: UserRole.USER
})
userWithoutFlag = await servers[0].login.getAccessToken(user)
const { videoChannels } = await servers[0].users.getMyInfo({ token: userWithoutFlag })
channelOfUserWithoutFlag = videoChannels[0].id
}
{
const user = { username: 'user_with_flag', password: 'password' }
await servers[0].users.create({
username: user.username,
adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST,
password: user.password,
role: UserRole.USER
})
userWithFlag = await servers[0].login.getAccessToken(user)
}
await waitJobs(servers)
})
it('Should auto blacklist a video on upload', async function () {
await servers[0].videos.upload({ token: userWithoutFlag, attributes: { name: 'blacklisted' } })
const body = await command.list({ type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
expect(body.total).to.equal(1)
expect(body.data[0].video.name).to.equal('blacklisted')
})
it('Should auto blacklist a video on URL import', async function () {
this.timeout(15000)
const attributes = {
targetUrl: FIXTURE_URLS.goodVideo,
name: 'URL import',
channelId: channelOfUserWithoutFlag
}
await servers[0].videoImports.importVideo({ token: userWithoutFlag, attributes })
const body = await command.list({ sort: 'createdAt', type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
expect(body.total).to.equal(2)
expect(body.data[1].video.name).to.equal('URL import')
})
it('Should auto blacklist a video on torrent import', async function () {
const attributes = {
magnetUri: FIXTURE_URLS.magnet,
name: 'Torrent import',
channelId: channelOfUserWithoutFlag
}
await servers[0].videoImports.importVideo({ token: userWithoutFlag, attributes })
const body = await command.list({ sort: 'createdAt', type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
expect(body.total).to.equal(3)
expect(body.data[2].video.name).to.equal('Torrent import')
})
it('Should not auto blacklist a video on upload if the user has the bypass blacklist flag', async function () {
await servers[0].videos.upload({ token: userWithFlag, attributes: { name: 'not blacklisted' } })
const body = await command.list({ type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
expect(body.total).to.equal(3)
})
})
after(async function () {
await cleanupTests(servers)
})
})
+189
ファイルの表示
@@ -0,0 +1,189 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
setDefaultAccountAvatar
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test watched words', function () {
let server: PeerTubeServer
let userToken: string
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultAccountAvatar([ server ])
userToken = await server.users.generateUserAndToken('user1')
})
function runTests (mode: 'server' | 'account') {
let listId: number
let accountName: string
let token: string
before(() => {
accountName = mode === 'server'
? undefined
: 'user1'
token = mode === 'server'
? server.accessToken
: userToken
})
it('Should list empty watched words', async function () {
const { data, total } = await server.watchedWordsLists.listWordsLists({ token, accountName })
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
})
it('Should add watched words lists', async function () {
{
const { watchedWordsList } = await server.watchedWordsLists.createList({
token,
listName: 'list user one',
words: [ 'word1' ],
accountName
})
listId = watchedWordsList.id
}
{
await server.watchedWordsLists.createList({
token,
listName: 'list user two',
words: [ 'word2', 'word3' ],
accountName
})
}
if (mode === 'account') {
await server.watchedWordsLists.createList({
listName: 'list one',
words: [ 'word4', 'word5' ],
accountName: 'root'
})
}
})
it('Should list watched words', async function () {
if (mode === 'account') {
const { data, total } = await server.watchedWordsLists.listWordsLists({ accountName: 'root' })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].id).to.exist
expect(data[0].createdAt).to.exist
expect(data[0].updatedAt).to.exist
expect(data[0].listName).to.equal('list one')
expect(data[0].words).to.deep.equal([ 'word4', 'word5' ])
}
// With sort, start, count
{
const { data, total } = await server.watchedWordsLists.listWordsLists({
token,
accountName,
sort: 'createdAt'
})
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
expect(data[0].listName).to.equal('list user one')
expect(data[0].words).to.deep.equal([ 'word1' ])
expect(data[1].listName).to.equal('list user two')
expect(data[1].words).to.deep.equal([ 'word2', 'word3' ])
}
{
const { data, total } = await server.watchedWordsLists.listWordsLists({
token,
accountName,
sort: '-listName'
})
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
expect(data[0].listName).to.equal('list user two')
expect(data[1].listName).to.equal('list user one')
}
{
const { data, total } = await server.watchedWordsLists.listWordsLists({
accountName,
token,
sort: '-listName',
start: 1,
count: 1
})
expect(total).to.equal(2)
expect(data).to.have.lengthOf(1)
expect(data[0].listName).to.equal('list user one')
}
})
it('Should update watched words lists', async function () {
await server.watchedWordsLists.updateList({
listId,
token,
accountName,
words: [ 'updated-word1', 'updated-word2' ]
})
await server.watchedWordsLists.updateList({
listId,
token,
accountName,
listName: 'updated-list'
})
const { data } = await server.watchedWordsLists.listWordsLists({ token, accountName })
const list = data.find(l => l.id === listId)
expect(list.listName).to.equal('updated-list')
expect(list.words).to.deep.equal([ 'updated-word1', 'updated-word2' ])
})
it('Should delete watched words lists', async function () {
await server.watchedWordsLists.deleteList({
listId,
token,
accountName
})
const { total, data } = await server.watchedWordsLists.listWordsLists({ token, accountName })
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
const list = data.find(l => l.id === listId)
expect(list).to.not.exist
})
}
describe('Managing account watched words', function () {
runTests('account')
})
describe('Managing instance watched words', function () {
runTests('server')
})
after(async function () {
await cleanupTests([ server ])
})
})
+154
ファイルの表示
@@ -0,0 +1,154 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { wait } from '@peertube/peertube-core-utils'
import { PluginType, UserNotification, UserNotificationType } from '@peertube/peertube-models'
import { cleanupTests, PeerTubeServer } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import { MockJoinPeerTubeVersions } from '@tests/shared/mock-servers/mock-joinpeertube-versions.js'
import { CheckerBaseParams, prepareNotificationsTest, checkNewPeerTubeVersion, checkNewPluginVersion } from '@tests/shared/notifications.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
describe('Test admin notifications', function () {
let server: PeerTubeServer
let sqlCommand: SQLCommand
let userNotifications: UserNotification[] = []
let adminNotifications: UserNotification[] = []
let emails: object[] = []
let baseParams: CheckerBaseParams
let joinPeerTubeServer: MockJoinPeerTubeVersions
before(async function () {
this.timeout(120000)
joinPeerTubeServer = new MockJoinPeerTubeVersions()
const port = await joinPeerTubeServer.initialize()
const config = {
peertube: {
check_latest_version: {
enabled: true,
url: `http://127.0.0.1:${port}/versions.json`
}
},
plugins: {
index: {
enabled: true,
check_latest_versions_interval: '3 seconds'
}
}
}
const res = await prepareNotificationsTest(1, config)
emails = res.emails
server = res.servers[0]
userNotifications = res.userNotifications
adminNotifications = res.adminNotifications
baseParams = {
server,
emails,
socketNotifications: adminNotifications,
token: server.accessToken
}
await server.plugins.install({ npmName: 'peertube-plugin-hello-world' })
await server.plugins.install({ npmName: 'peertube-theme-background-red' })
sqlCommand = new SQLCommand(server)
})
describe('Latest PeerTube version notification', function () {
it('Should not send a notification to admins if there is no new version', async function () {
this.timeout(30000)
joinPeerTubeServer.setLatestVersion('1.4.2')
await wait(4500)
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' })
})
it('Should send a notification to admins on new version', async function () {
this.timeout(30000)
joinPeerTubeServer.setLatestVersion('15.4.2')
await wait(4500)
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.2', checkType: 'presence' })
})
it('Should not send the same notification to admins', async function () {
this.timeout(30000)
await wait(4500)
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
})
it('Should not have sent a notification to users', async function () {
this.timeout(30000)
expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(0)
})
it('Should send a new notification after a new release', async function () {
this.timeout(30000)
joinPeerTubeServer.setLatestVersion('15.4.3')
await wait(4500)
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.3', checkType: 'presence' })
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
})
})
describe('Latest plugin version notification', function () {
it('Should not send a notification to admins if there is no new plugin version', async function () {
this.timeout(30000)
await wait(6000)
await checkNewPluginVersion({ ...baseParams, pluginType: PluginType.PLUGIN, pluginName: 'hello-world', checkType: 'absence' })
})
it('Should send a notification to admins on new plugin version', async function () {
this.timeout(30000)
await sqlCommand.setPluginVersion('hello-world', '0.0.1')
await sqlCommand.setPluginLatestVersion('hello-world', '0.0.1')
await wait(6000)
await checkNewPluginVersion({ ...baseParams, pluginType: PluginType.PLUGIN, pluginName: 'hello-world', checkType: 'presence' })
})
it('Should not send the same notification to admins', async function () {
this.timeout(30000)
await wait(6000)
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(1)
})
it('Should not have sent a notification to users', async function () {
expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(0)
})
it('Should send a new notification after a new plugin release', async function () {
this.timeout(30000)
await sqlCommand.setPluginVersion('hello-world', '0.0.1')
await sqlCommand.setPluginLatestVersion('hello-world', '0.0.1')
await wait(6000)
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await sqlCommand.cleanup()
await cleanupTests([ server ])
})
})
+81
ファイルの表示
@@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { UserNotification } from '@peertube/peertube-models'
import { PeerTubeServer, cleanupTests, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import {
CheckerBaseParams,
checkMyVideoTranscriptionGenerated,
prepareNotificationsTest
} from '@tests/shared/notifications.js'
import { join } from 'path'
describe('Test caption notifications', function () {
let servers: PeerTubeServer[] = []
let userNotifications: UserNotification[] = []
let emails: object[] = []
let userAccessToken: string
before(async function () {
this.timeout(120000)
const res = await prepareNotificationsTest(1)
emails = res.emails
userAccessToken = res.userAccessToken
servers = res.servers
userNotifications = res.userNotifications
})
describe('Transcription of my video generated is published', function () {
const language = { id: 'en', label: 'English' }
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userAccessToken
}
})
async function uploadAndWait () {
const { uuid } = await servers[0].videos.upload({
token: userAccessToken,
attributes: {
name: 'video',
fixture: join('transcription', 'videos', 'the_last_man_on_earth.mp4'),
language: undefined
}
})
await waitJobs(servers)
return servers[0].videos.get({ id: uuid })
}
it('Should not send a notification if transcription is not enabled', async function () {
this.timeout(50000)
const { name, shortUUID } = await uploadAndWait()
await checkMyVideoTranscriptionGenerated({ ...baseParams, videoName: name, shortUUID, language, checkType: 'absence' })
})
it('Should send a notification transcription is enabled', async function () {
this.timeout(240000)
await servers[0].config.enableTranscription()
const { name, shortUUID } = await uploadAndWait()
await checkMyVideoTranscriptionGenerated({ ...baseParams, videoName: name, shortUUID, language, checkType: 'presence' })
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests(servers)
})
})
+399
ファイルの表示
@@ -0,0 +1,399 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { UserNotification, UserNotificationType, VideoCommentPolicy } from '@peertube/peertube-models'
import { PeerTubeServer, cleanupTests, setDefaultAccountAvatar, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import { CheckerBaseParams, checkCommentMention, checkNewCommentOnMyVideo, prepareNotificationsTest } from '@tests/shared/notifications.js'
import { expect } from 'chai'
describe('Test comments notifications', function () {
let servers: PeerTubeServer[] = []
let userToken: string
let userToken2: string
let userNotifications: UserNotification[] = []
let emails: object[] = []
const commentText = '**hello** <a href="https://joinpeertube.org">world</a>, <h1>what do you think about peertube?</h1>'
const expectedHtml = '<strong>hello</strong> <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">world</a>' +
', </p>what do you think about peertube?'
before(async function () {
this.timeout(120000)
const res = await prepareNotificationsTest(2)
emails = res.emails
userToken = res.userAccessToken
servers = res.servers
userNotifications = res.userNotifications
userToken2 = await servers[0].users.generateUserAndToken('user2')
await setDefaultAccountAvatar(servers[0], userToken2)
})
describe('Comment on my video notifications', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken
}
})
it('Should not send a new comment notification after a comment on another video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
const commentId = created.id
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'absence' })
})
it('Should not send a new comment notification if I comment my own video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
const created = await servers[0].comments.createThread({ token: userToken, videoId: uuid, text: 'comment' })
const commentId = created.id
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'absence' })
})
it('Should not send a new comment notification if the account is muted', async function () {
this.timeout(60000)
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
const commentId = created.id
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'absence' })
await servers[0].blocklist.removeFromMyBlocklist({ token: userToken, account: 'root' })
})
it('Should send a new comment notification after a local comment on my video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
const created = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
const commentId = created.id
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'presence' })
})
it('Should send a new comment notification after a remote comment on my video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
await waitJobs(servers)
await servers[1].comments.createThread({ videoId: uuid, text: 'comment' })
await waitJobs(servers)
const { data } = await servers[0].comments.listThreads({ videoId: uuid })
expect(data).to.have.lengthOf(1)
const commentId = data[0].id
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'presence' })
})
it('Should send a new comment notification after a local reply on my video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: 'comment' })
const { id: commentId } = await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'reply' })
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId, commentId, checkType: 'presence' })
})
it('Should send a new comment notification after a remote reply on my video', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
await waitJobs(servers)
{
const created = await servers[1].comments.createThread({ videoId: uuid, text: 'comment' })
const threadId = created.id
await servers[1].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'reply' })
}
await waitJobs(servers)
const { data } = await servers[0].comments.listThreads({ videoId: uuid })
expect(data).to.have.lengthOf(1)
const threadId = data[0].id
const tree = await servers[0].comments.getThread({ videoId: uuid, threadId })
expect(tree.children).to.have.lengthOf(1)
const commentId = tree.children[0].comment.id
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId, commentId, checkType: 'presence' })
})
it('Should send a new comment notification of a comment that requires approval', async function () {
this.timeout(60000)
const { id: videoId, uuid, shortUUID } = await servers[0].videos.upload({
token: userToken,
attributes: { name: 'super video', commentsPolicy: VideoCommentPolicy.REQUIRES_APPROVAL }
})
await waitJobs(servers)
let localCommentId: number
{
const created = await servers[0].comments.createThread({ videoId: uuid, text: 'local approval', token: userToken2 })
const commentId = localCommentId = created.id
await waitJobs(servers)
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'presence', approval: true })
}
{
await servers[1].comments.createThread({ videoId: uuid, text: 'remote approval' })
await waitJobs(servers)
const commentId = await servers[0].comments.findCommentId({ token: userToken, videoId, text: 'remote approval' })
await checkNewCommentOnMyVideo({ ...baseParams, shortUUID, threadId: commentId, commentId, checkType: 'presence', approval: true })
}
// It should not re-notify on approval
{
await servers[0].comments.approve({ token: userToken, commentId: localCommentId, videoId: shortUUID })
await waitJobs(servers)
const notifications = baseParams.socketNotifications
.filter(n => n.type === UserNotificationType.NEW_COMMENT_ON_MY_VIDEO && n.comment?.video?.shortUUID === shortUUID)
expect(notifications).to.have.lengthOf(2)
}
})
it('Should convert markdown in comment to html', async function () {
this.timeout(60000)
const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'cool video' } })
await servers[0].comments.createThread({ videoId: uuid, text: commentText })
await waitJobs(servers)
const latestEmail = emails[emails.length - 1]
expect(latestEmail['html']).to.contain(expectedHtml)
})
})
describe('Mention notifications', function () {
let baseParams: CheckerBaseParams
const byAccountDisplayName = 'super root name'
before(async function () {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken
}
await servers[0].users.updateMe({ displayName: 'super root name' })
await servers[1].users.updateMe({ displayName: 'super root 2 name' })
})
it('Should not send a new mention comment notification if I mention the video owner', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
const { id: commentId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
await waitJobs(servers)
await checkCommentMention({ ...baseParams, shortUUID, threadId: commentId, commentId, byAccountDisplayName, checkType: 'absence' })
})
it('Should not send a new mention comment notification if I mention myself', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
const { id: commentId } = await servers[0].comments.createThread({ token: userToken, videoId: uuid, text: '@user_1 hello' })
await waitJobs(servers)
await checkCommentMention({ ...baseParams, shortUUID, threadId: commentId, commentId, byAccountDisplayName, checkType: 'absence' })
})
it('Should not send a new mention notification if the account is muted', async function () {
this.timeout(60000)
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
const { id: commentId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
await waitJobs(servers)
await checkCommentMention({ ...baseParams, shortUUID, threadId: commentId, commentId, byAccountDisplayName, checkType: 'absence' })
await servers[0].blocklist.removeFromMyBlocklist({ token: userToken, account: 'root' })
})
it('Should not send a new mention notification if the remote account mention a local account', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
await waitJobs(servers)
const { id: threadId } = await servers[1].comments.createThread({ videoId: uuid, text: '@user_1 hello' })
await waitJobs(servers)
const byAccountDisplayName = 'super root 2 name'
await checkCommentMention({ ...baseParams, shortUUID, threadId, commentId: threadId, byAccountDisplayName, checkType: 'absence' })
})
it('Should send a new mention notification after local comments', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hellotext: 1' })
await waitJobs(servers)
await checkCommentMention({ ...baseParams, shortUUID, threadId, commentId: threadId, byAccountDisplayName, checkType: 'presence' })
const { id: commentId } = await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: 'hello 2 @user_1' })
await waitJobs(servers)
await checkCommentMention({ ...baseParams, shortUUID, commentId, threadId, byAccountDisplayName, checkType: 'presence' })
})
it('Should send a new mention notification after remote comments', async function () {
this.timeout(60000)
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
await waitJobs(servers)
const text1 = `hello @user_1@${servers[0].host} 1`
const { id: server2ThreadId } = await servers[1].comments.createThread({ videoId: uuid, text: text1 })
await waitJobs(servers)
const { data } = await servers[0].comments.listThreads({ videoId: uuid })
expect(data).to.have.lengthOf(1)
const byAccountDisplayName = 'super root 2 name'
const threadId = data[0].id
await checkCommentMention({ ...baseParams, shortUUID, commentId: threadId, threadId, byAccountDisplayName, checkType: 'presence' })
const text2 = `@user_1@${servers[0].host} hello 2 @root@${servers[0].host}`
await servers[1].comments.addReply({ videoId: uuid, toCommentId: server2ThreadId, text: text2 })
await waitJobs(servers)
const tree = await servers[0].comments.getThread({ videoId: uuid, threadId })
expect(tree.children).to.have.lengthOf(1)
const commentId = tree.children[0].comment.id
await checkCommentMention({ ...baseParams, shortUUID, commentId, threadId, byAccountDisplayName, checkType: 'presence' })
})
it('Should not send a new mention notification before approval', async function () {
this.timeout(60000)
const { id: videoId, uuid, shortUUID } = await servers[0].videos.upload({
attributes: { name: 'super video', commentsPolicy: VideoCommentPolicy.REQUIRES_APPROVAL }
})
await waitJobs(servers)
const localText = '@user_1 local approval'
{
const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: localText, token: userToken2 })
await waitJobs(servers)
await checkCommentMention({
...baseParams,
shortUUID,
threadId,
commentId: threadId,
byAccountDisplayName: 'user2',
checkType: 'absence'
})
}
const remoteText = `@user_1@${servers[0].host} remote approval`
{
await servers[1].comments.createThread({ videoId: uuid, text: remoteText })
await waitJobs(servers)
const threadId = await servers[0].comments.findCommentId({ token: userToken, videoId, text: remoteText })
const byAccountDisplayName = 'super root 2 name'
await checkCommentMention({ ...baseParams, shortUUID, threadId, commentId: threadId, byAccountDisplayName, checkType: 'absence' })
}
// It should notify on approval
{
const toTest = [
{ text: localText, byAccountDisplayName: 'user2' },
{ text: remoteText, byAccountDisplayName: 'super root 2 name' }
]
for (const { text, byAccountDisplayName } of toTest) {
const localCommentId = await servers[0].comments.findCommentId({ token: userToken, videoId, text })
await servers[0].comments.approve({ commentId: localCommentId, videoId: shortUUID })
await waitJobs(servers)
await checkCommentMention({
...baseParams,
shortUUID,
threadId: localCommentId,
commentId: localCommentId,
byAccountDisplayName,
checkType: 'presence'
})
}
}
})
it('Should convert markdown in comment to html', async function () {
this.timeout(60000)
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
const { id: threadId } = await servers[0].comments.createThread({ videoId: uuid, text: '@user_1 hello 1' })
await servers[0].comments.addReply({ videoId: uuid, toCommentId: threadId, text: '@user_1 ' + commentText })
await waitJobs(servers)
const latestEmail = emails[emails.length - 1]
expect(latestEmail['html']).to.contain(expectedHtml)
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests(servers)
})
})
+7
ファイルの表示
@@ -0,0 +1,7 @@
import './admin-notifications.js'
import './captions-notifications.js'
import './comments-notifications.js'
import './moderation-notifications.js'
import './notifications-api.js'
import './registrations-notifications.js'
import './user-notifications.js'
+590
ファイルの表示
@@ -0,0 +1,590 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import { AbuseState, UserNotification, UserRole, VideoPrivacy } from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
import { cleanupTests, PeerTubeServer, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import { MockInstancesIndex } from '@tests/shared/mock-servers/mock-instances-index.js'
import {
prepareNotificationsTest,
CheckerBaseParams,
checkNewVideoAbuseForModerators,
checkNewCommentAbuseForModerators,
checkNewAccountAbuseForModerators,
checkAbuseStateChange,
checkNewAbuseMessage,
checkNewBlacklistOnMyVideo,
checkNewInstanceFollower,
checkAutoInstanceFollowing,
checkVideoAutoBlacklistForModerators,
checkMyVideoIsPublished,
checkNewVideoFromSubscription
} from '@tests/shared/notifications.js'
describe('Test moderation notifications', function () {
let servers: PeerTubeServer[] = []
let userToken1: string
let userToken2: string
let userNotifications: UserNotification[] = []
let adminNotifications: UserNotification[] = []
let adminNotificationsServer2: UserNotification[] = []
let emails: object[] = []
before(async function () {
this.timeout(120000)
const res = await prepareNotificationsTest(3)
emails = res.emails
userToken1 = res.userAccessToken
servers = res.servers
userNotifications = res.userNotifications
adminNotifications = res.adminNotifications
adminNotificationsServer2 = res.adminNotificationsServer2
userToken2 = await servers[1].users.generateUserAndToken('user2', UserRole.USER)
})
describe('Abuse for moderators notification', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: adminNotifications,
token: servers[0].accessToken
}
})
it('Should not send a notification to moderators on local abuse reported by an admin', async function () {
this.timeout(50000)
const name = 'video for abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await servers[0].abuses.report({ videoId: video.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'absence' })
})
it('Should send a notification to moderators on local video abuse', async function () {
this.timeout(50000)
const name = 'video for abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
})
it('Should send a notification to moderators on remote video abuse', async function () {
this.timeout(50000)
const name = 'video for abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await waitJobs(servers)
const videoId = await servers[1].videos.getId({ uuid: video.uuid })
await servers[1].abuses.report({ token: userToken2, videoId, reason: 'super reason' })
await waitJobs(servers)
await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
})
it('Should send a notification to moderators on local comment abuse', async function () {
this.timeout(50000)
const name = 'video for abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
const comment = await servers[0].comments.createThread({
token: userToken1,
videoId: video.id,
text: 'comment abuse ' + buildUUID()
})
await waitJobs(servers)
await servers[0].abuses.report({ token: userToken1, commentId: comment.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
})
it('Should send a notification to moderators on remote comment abuse', async function () {
this.timeout(50000)
const name = 'video for abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await servers[0].comments.createThread({
token: userToken1,
videoId: video.id,
text: 'comment abuse ' + buildUUID()
})
await waitJobs(servers)
const { data } = await servers[1].comments.listThreads({ videoId: video.uuid })
const commentId = data[0].id
await servers[1].abuses.report({ token: userToken2, commentId, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
})
it('Should send a notification to moderators on local account abuse', async function () {
this.timeout(50000)
const username = 'user' + new Date().getTime()
const { account } = await servers[0].users.create({ username, password: 'donald' })
const accountId = account.id
await servers[0].abuses.report({ token: userToken1, accountId, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' })
})
it('Should send a notification to moderators on remote account abuse', async function () {
this.timeout(50000)
const username = 'user' + new Date().getTime()
const tmpToken = await servers[0].users.generateUserAndToken(username)
await servers[0].videos.upload({ token: tmpToken, attributes: { name: 'super video' } })
await waitJobs(servers)
const account = await servers[1].accounts.get({ accountName: username + '@' + servers[0].host })
await servers[1].abuses.report({ token: userToken2, accountId: account.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' })
})
})
describe('Abuse state change notification', function () {
let baseParams: CheckerBaseParams
let abuseId: number
before(async function () {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken1
}
const name = 'abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
abuseId = body.abuse.id
})
it('Should send a notification to reporter if the abuse has been accepted', async function () {
this.timeout(30000)
await servers[0].abuses.update({ abuseId, body: { state: AbuseState.ACCEPTED } })
await waitJobs(servers)
await checkAbuseStateChange({ ...baseParams, abuseId, state: AbuseState.ACCEPTED, checkType: 'presence' })
})
it('Should send a notification to reporter if the abuse has been rejected', async function () {
this.timeout(30000)
await servers[0].abuses.update({ abuseId, body: { state: AbuseState.REJECTED } })
await waitJobs(servers)
await checkAbuseStateChange({ ...baseParams, abuseId, state: AbuseState.REJECTED, checkType: 'presence' })
})
})
describe('New abuse message notification', function () {
let baseParamsUser: CheckerBaseParams
let baseParamsAdmin: CheckerBaseParams
let abuseId: number
let abuseId2: number
before(async function () {
baseParamsUser = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken1
}
baseParamsAdmin = {
server: servers[0],
emails,
socketNotifications: adminNotifications,
token: servers[0].accessToken
}
const name = 'abuse ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
{
const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
abuseId = body.abuse.id
}
{
const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason 2' })
abuseId2 = body.abuse.id
}
})
it('Should send a notification to reporter on new message', async function () {
this.timeout(30000)
const message = 'my super message to users'
await servers[0].abuses.addMessage({ abuseId, message })
await waitJobs(servers)
await checkNewAbuseMessage({ ...baseParamsUser, abuseId, message, toEmail: 'user_1@example.com', checkType: 'presence' })
})
it('Should not send a notification to the admin if sent by the admin', async function () {
this.timeout(30000)
const message = 'my super message that should not be sent to the admin'
await servers[0].abuses.addMessage({ abuseId, message })
await waitJobs(servers)
const toEmail = 'admin' + servers[0].internalServerNumber + '@example.com'
await checkNewAbuseMessage({ ...baseParamsAdmin, abuseId, message, toEmail, checkType: 'absence' })
})
it('Should send a notification to moderators', async function () {
this.timeout(30000)
const message = 'my super message to moderators'
await servers[0].abuses.addMessage({ token: userToken1, abuseId: abuseId2, message })
await waitJobs(servers)
const toEmail = 'admin' + servers[0].internalServerNumber + '@example.com'
await checkNewAbuseMessage({ ...baseParamsAdmin, abuseId: abuseId2, message, toEmail, checkType: 'presence' })
})
it('Should not send a notification to reporter if sent by the reporter', async function () {
this.timeout(30000)
const message = 'my super message that should not be sent to reporter'
await servers[0].abuses.addMessage({ token: userToken1, abuseId: abuseId2, message })
await waitJobs(servers)
const toEmail = 'user_1@example.com'
await checkNewAbuseMessage({ ...baseParamsUser, abuseId: abuseId2, message, toEmail, checkType: 'absence' })
})
})
describe('Video blacklist on my video', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken1
}
})
it('Should send a notification to video owner on blacklist', async function () {
this.timeout(30000)
const name = 'video for abuse ' + buildUUID()
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await servers[0].blacklist.add({ videoId: uuid })
await waitJobs(servers)
await checkNewBlacklistOnMyVideo({ ...baseParams, shortUUID, videoName: name, blacklistType: 'blacklist' })
})
it('Should send a notification to video owner on unblacklist', async function () {
this.timeout(30000)
const name = 'video for abuse ' + buildUUID()
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
await servers[0].blacklist.add({ videoId: uuid })
await waitJobs(servers)
await servers[0].blacklist.remove({ videoId: uuid })
await waitJobs(servers)
await wait(500)
await checkNewBlacklistOnMyVideo({ ...baseParams, shortUUID, videoName: name, blacklistType: 'unblacklist' })
})
})
describe('New instance follows', function () {
const instanceIndexServer = new MockInstancesIndex()
let config: any
let baseParams: CheckerBaseParams
before(async function () {
baseParams = {
server: servers[0],
emails,
socketNotifications: adminNotifications,
token: servers[0].accessToken
}
const port = await instanceIndexServer.initialize()
instanceIndexServer.addInstance(servers[1].host)
config = {
followings: {
instance: {
autoFollowIndex: {
indexUrl: `http://127.0.0.1:${port}/api/v1/instances/hosts`,
enabled: true
}
}
}
}
})
it('Should send a notification only to admin when there is a new instance follower', async function () {
this.timeout(60000)
await servers[2].follows.follow({ hosts: [ servers[0].url ] })
await waitJobs(servers)
await checkNewInstanceFollower({ ...baseParams, followerHost: servers[2].host, checkType: 'presence' })
const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
await checkNewInstanceFollower({ ...baseParams, ...userOverride, followerHost: servers[2].host, checkType: 'absence' })
})
it('Should send a notification on auto follow back', async function () {
this.timeout(40000)
await servers[2].follows.unfollow({ target: servers[0] })
await waitJobs(servers)
const config = {
followings: {
instance: {
autoFollowBack: { enabled: true }
}
}
}
await servers[0].config.updateExistingConfig({ newConfig: config })
await servers[2].follows.follow({ hosts: [ servers[0].url ] })
await waitJobs(servers)
const followerHost = servers[0].host
const followingHost = servers[2].host
await checkAutoInstanceFollowing({ ...baseParams, followerHost, followingHost, checkType: 'presence' })
const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
await checkAutoInstanceFollowing({ ...baseParams, ...userOverride, followerHost, followingHost, checkType: 'absence' })
config.followings.instance.autoFollowBack.enabled = false
await servers[0].config.updateExistingConfig({ newConfig: config })
await servers[0].follows.unfollow({ target: servers[2] })
await servers[2].follows.unfollow({ target: servers[0] })
})
it('Should send a notification on auto instances index follow', async function () {
this.timeout(30000)
await servers[0].follows.unfollow({ target: servers[1] })
await servers[0].config.updateExistingConfig({ newConfig: config })
await wait(5000)
await waitJobs(servers)
const followerHost = servers[0].host
const followingHost = servers[1].host
await checkAutoInstanceFollowing({ ...baseParams, followerHost, followingHost, checkType: 'presence' })
config.followings.instance.autoFollowIndex.enabled = false
await servers[0].config.updateExistingConfig({ newConfig: config })
await servers[0].follows.unfollow({ target: servers[1] })
})
})
describe('Video-related notifications when video auto-blacklist is enabled', function () {
let userBaseParams: CheckerBaseParams
let adminBaseParamsServer1: CheckerBaseParams
let adminBaseParamsServer2: CheckerBaseParams
let uuid: string
let shortUUID: string
let videoName: string
before(async function () {
adminBaseParamsServer1 = {
server: servers[0],
emails,
socketNotifications: adminNotifications,
token: servers[0].accessToken
}
adminBaseParamsServer2 = {
server: servers[1],
emails,
socketNotifications: adminNotificationsServer2,
token: servers[1].accessToken
}
userBaseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userToken1
}
await servers[0].config.enableAutoBlacklist()
await servers[0].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
await servers[1].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
})
it('Should send notification to moderators on new video with auto-blacklist', async function () {
this.timeout(120000)
videoName = 'video with auto-blacklist ' + buildUUID()
const video = await servers[0].videos.upload({ token: userToken1, attributes: { name: videoName } })
shortUUID = video.shortUUID
uuid = video.uuid
await waitJobs(servers)
await checkVideoAutoBlacklistForModerators({ ...adminBaseParamsServer1, shortUUID, videoName, checkType: 'presence' })
})
it('Should not send video publish notification if auto-blacklisted', async function () {
this.timeout(120000)
await checkMyVideoIsPublished({ ...userBaseParams, videoName, shortUUID, checkType: 'absence' })
})
it('Should not send a local user subscription notification if auto-blacklisted', async function () {
this.timeout(120000)
await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'absence' })
})
it('Should not send a remote user subscription notification if auto-blacklisted', async function () {
await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName, shortUUID, checkType: 'absence' })
})
it('Should send video published and unblacklist after video unblacklisted', async function () {
this.timeout(120000)
await servers[0].blacklist.remove({ videoId: uuid })
await waitJobs(servers)
// FIXME: Can't test as two notifications sent to same user and util only checks last one
// One notification might be better anyways
// await checkNewBlacklistOnMyVideo(userBaseParams, videoUUID, videoName, 'unblacklist')
// await checkVideoIsPublished(userBaseParams, videoName, videoUUID, 'presence')
})
it('Should send a local user subscription notification after removed from blacklist', async function () {
this.timeout(120000)
await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'presence' })
})
it('Should send a remote user subscription notification after removed from blacklist', async function () {
this.timeout(120000)
await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName, shortUUID, checkType: 'presence' })
})
it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () {
this.timeout(120000)
const updateAt = new Date(new Date().getTime() + 1000000)
const name = 'video with auto-blacklist and future schedule ' + buildUUID()
const attributes = {
name,
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { shortUUID, uuid } = await servers[0].videos.upload({ token: userToken1, attributes })
await servers[0].blacklist.remove({ videoId: uuid })
await waitJobs(servers)
await checkNewBlacklistOnMyVideo({ ...userBaseParams, shortUUID, videoName: name, blacklistType: 'unblacklist' })
// FIXME: Can't test absence as two notifications sent to same user and util only checks last one
// One notification might be better anyways
// await checkVideoIsPublished(userBaseParams, name, uuid, 'absence')
await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName: name, shortUUID, checkType: 'absence' })
await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should not send publish/subscription notifications after scheduled update if video still auto-blacklisted', async function () {
this.timeout(120000)
// In 2 seconds
const updateAt = new Date(new Date().getTime() + 2000)
const name = 'video with schedule done and still auto-blacklisted ' + buildUUID()
const attributes = {
name,
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes })
await wait(6000)
await checkMyVideoIsPublished({ ...userBaseParams, videoName: name, shortUUID, checkType: 'absence' })
await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName: name, shortUUID, checkType: 'absence' })
await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should not send a notification to moderators on new video without auto-blacklist', async function () {
this.timeout(120000)
const name = 'video without auto-blacklist ' + buildUUID()
// admin with blacklist right will not be auto-blacklisted
const { shortUUID } = await servers[0].videos.upload({ attributes: { name } })
await waitJobs(servers)
await checkVideoAutoBlacklistForModerators({ ...adminBaseParamsServer1, shortUUID, videoName: name, checkType: 'absence' })
})
after(async () => {
await servers[0].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
await servers[1].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests(servers)
})
})
+206
ファイルの表示
@@ -0,0 +1,206 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { UserNotification, UserNotificationSettingValue } from '@peertube/peertube-models'
import { cleanupTests, PeerTubeServer, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import {
prepareNotificationsTest,
CheckerBaseParams,
getAllNotificationsSettings,
checkNewVideoFromSubscription
} from '@tests/shared/notifications.js'
describe('Test notifications API', function () {
let server: PeerTubeServer
let userNotifications: UserNotification[] = []
let userToken: string
let emails: object[] = []
before(async function () {
this.timeout(240000)
const res = await prepareNotificationsTest(1)
emails = res.emails
userToken = res.userAccessToken
userNotifications = res.userNotifications
server = res.servers[0]
await server.subscriptions.add({ token: userToken, targetUri: 'root_channel@' + server.host })
for (let i = 0; i < 10; i++) {
await server.videos.randomUpload({ wait: false })
}
await waitJobs([ server ])
})
describe('Notification list & count', function () {
it('Should correctly list notifications', async function () {
const { data, total } = await server.notifications.list({ token: userToken, start: 0, count: 2 })
expect(data).to.have.lengthOf(2)
expect(total).to.equal(10)
})
})
describe('Mark as read', function () {
it('Should mark as read some notifications', async function () {
const { data } = await server.notifications.list({ token: userToken, start: 2, count: 3 })
const ids = data.map(n => n.id)
await server.notifications.markAsRead({ token: userToken, ids })
})
it('Should have the notifications marked as read', async function () {
const { data } = await server.notifications.list({ token: userToken, start: 0, count: 10 })
expect(data[0].read).to.be.false
expect(data[1].read).to.be.false
expect(data[2].read).to.be.true
expect(data[3].read).to.be.true
expect(data[4].read).to.be.true
expect(data[5].read).to.be.false
})
it('Should only list read notifications', async function () {
const { data } = await server.notifications.list({ token: userToken, start: 0, count: 10, unread: false })
for (const notification of data) {
expect(notification.read).to.be.true
}
})
it('Should only list unread notifications', async function () {
const { data } = await server.notifications.list({ token: userToken, start: 0, count: 10, unread: true })
for (const notification of data) {
expect(notification.read).to.be.false
}
})
it('Should mark as read all notifications', async function () {
await server.notifications.markAsReadAll({ token: userToken })
const body = await server.notifications.list({ token: userToken, start: 0, count: 10, unread: true })
expect(body.total).to.equal(0)
expect(body.data).to.have.lengthOf(0)
})
})
describe('Notification settings', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server,
emails,
socketNotifications: userNotifications,
token: userToken
}
})
it('Should not have notifications', async function () {
this.timeout(40000)
await server.notifications.updateMySettings({
token: userToken,
settings: { ...getAllNotificationsSettings(), newVideoFromSubscription: UserNotificationSettingValue.NONE }
})
{
const info = await server.users.getMyInfo({ token: userToken })
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE)
}
const { name, shortUUID } = await server.videos.randomUpload()
const check = { web: true, mail: true }
await checkNewVideoFromSubscription({ ...baseParams, check, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should only have web notifications', async function () {
this.timeout(20000)
await server.notifications.updateMySettings({
token: userToken,
settings: { ...getAllNotificationsSettings(), newVideoFromSubscription: UserNotificationSettingValue.WEB }
})
{
const info = await server.users.getMyInfo({ token: userToken })
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB)
}
const { name, shortUUID } = await server.videos.randomUpload()
{
const check = { mail: true, web: false }
await checkNewVideoFromSubscription({ ...baseParams, check, videoName: name, shortUUID, checkType: 'absence' })
}
{
const check = { mail: false, web: true }
await checkNewVideoFromSubscription({ ...baseParams, check, videoName: name, shortUUID, checkType: 'presence' })
}
})
it('Should only have mail notifications', async function () {
this.timeout(20000)
await server.notifications.updateMySettings({
token: userToken,
settings: { ...getAllNotificationsSettings(), newVideoFromSubscription: UserNotificationSettingValue.EMAIL }
})
{
const info = await server.users.getMyInfo({ token: userToken })
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL)
}
const { name, shortUUID } = await server.videos.randomUpload()
{
const check = { mail: false, web: true }
await checkNewVideoFromSubscription({ ...baseParams, check, videoName: name, shortUUID, checkType: 'absence' })
}
{
const check = { mail: true, web: false }
await checkNewVideoFromSubscription({ ...baseParams, check, videoName: name, shortUUID, checkType: 'presence' })
}
})
it('Should have email and web notifications', async function () {
this.timeout(20000)
await server.notifications.updateMySettings({
token: userToken,
settings: {
...getAllNotificationsSettings(),
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
}
})
{
const info = await server.users.getMyInfo({ token: userToken })
expect(info.notificationSettings.newVideoFromSubscription).to.equal(
UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
)
}
const { name, shortUUID } = await server.videos.randomUpload()
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests([ server ])
})
})
+83
ファイルの表示
@@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { UserNotification } from '@peertube/peertube-models'
import { cleanupTests, PeerTubeServer, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import { CheckerBaseParams, prepareNotificationsTest, checkUserRegistered, checkRegistrationRequest } from '@tests/shared/notifications.js'
describe('Test registrations notifications', function () {
let server: PeerTubeServer
let userToken1: string
let userNotifications: UserNotification[] = []
let adminNotifications: UserNotification[] = []
let emails: object[] = []
let baseParams: CheckerBaseParams
before(async function () {
this.timeout(120000)
const res = await prepareNotificationsTest(1)
server = res.servers[0]
emails = res.emails
userToken1 = res.userAccessToken
adminNotifications = res.adminNotifications
userNotifications = res.userNotifications
baseParams = {
server,
emails,
socketNotifications: adminNotifications,
token: server.accessToken
}
})
describe('New direct registration for moderators', function () {
before(async function () {
await server.config.enableSignup(false)
})
it('Should send a notification only to moderators when a user registers on the instance', async function () {
this.timeout(50000)
await server.registrations.register({ username: 'user_10' })
await waitJobs([ server ])
await checkUserRegistered({ ...baseParams, username: 'user_10', checkType: 'presence' })
const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
await checkUserRegistered({ ...baseParams, ...userOverride, username: 'user_10', checkType: 'absence' })
})
})
describe('New registration request for moderators', function () {
before(async function () {
await server.config.enableSignup(true)
})
it('Should send a notification on new registration request', async function () {
this.timeout(50000)
const registrationReason = 'my reason'
await server.registrations.requestRegistration({ username: 'user_11', registrationReason })
await waitJobs([ server ])
await checkRegistrationRequest({ ...baseParams, username: 'user_11', registrationReason, checkType: 'presence' })
const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
await checkRegistrationRequest({ ...baseParams, ...userOverride, username: 'user_11', registrationReason, checkType: 'absence' })
})
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests([ server ])
})
})
+647
ファイルの表示
@@ -0,0 +1,647 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { wait } from '@peertube/peertube-core-utils'
import { UserNotification, UserNotificationType, VideoPrivacy, VideoStudioTask } from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
import { cleanupTests, findExternalSavedVideo, PeerTubeServer, stopFfmpeg, waitJobs } from '@peertube/peertube-server-commands'
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
import {
prepareNotificationsTest,
CheckerBaseParams,
checkNewVideoFromSubscription,
checkMyVideoIsPublished,
checkVideoStudioEditionIsFinished,
checkMyVideoImportIsFinished,
checkNewActorFollow,
checkNewLiveFromSubscription,
waitUntilNotification
} from '@tests/shared/notifications.js'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { uploadRandomVideoOnServers } from '@tests/shared/videos.js'
describe('Test user notifications', function () {
let servers: PeerTubeServer[] = []
let userAccessToken: string
let userNotifications: UserNotification[] = []
let adminNotifications: UserNotification[] = []
let adminNotificationsServer2: UserNotification[] = []
let emails: object[] = []
let channelId: number
before(async function () {
this.timeout(120000)
const res = await prepareNotificationsTest(3)
emails = res.emails
userAccessToken = res.userAccessToken
servers = res.servers
userNotifications = res.userNotifications
adminNotifications = res.adminNotifications
adminNotificationsServer2 = res.adminNotificationsServer2
channelId = res.channelId
})
describe('New video from my subscription notification', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userAccessToken
}
})
it('Should not send notifications if the user does not follow the video publisher', async function () {
this.timeout(50000)
await uploadRandomVideoOnServers(servers, 1)
const notification = await servers[0].notifications.getLatest({ token: userAccessToken })
expect(notification).to.be.undefined
expect(emails).to.have.lengthOf(0)
expect(userNotifications).to.have.lengthOf(0)
})
it('Should send a new video notification if the user follows the local video publisher', async function () {
await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'root_channel@' + servers[0].host })
await waitJobs(servers)
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 1)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a new video notification from a remote account', async function () {
this.timeout(150000) // Server 2 has transcoding enabled
await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'root_channel@' + servers[1].host })
await waitJobs(servers)
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a new video notification on a scheduled publication', async function () {
this.timeout(50000)
// In 2 seconds
const updateAt = new Date(new Date().getTime() + 2000)
const data = {
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 1, data)
await wait(6000)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a new video notification on a remote scheduled publication', async function () {
this.timeout(100000)
// In 2 seconds
const updateAt = new Date(new Date().getTime() + 2000)
const data = {
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
await waitJobs(servers)
await wait(6000)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should not send a notification before the video is published', async function () {
this.timeout(150000)
const updateAt = new Date(new Date().getTime() + 1000000)
const data = {
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 1, data)
await wait(6000)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should send a new video notification when a video becomes public', async function () {
this.timeout(50000)
const data = { privacy: VideoPrivacy.PRIVATE }
const { name, uuid, shortUUID } = await uploadRandomVideoOnServers(servers, 1, data)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
await servers[0].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
await waitJobs(servers)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a new video notification when a remote video becomes public', async function () {
this.timeout(120000)
const data = { privacy: VideoPrivacy.PRIVATE }
const { name, uuid, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
await servers[1].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
await waitJobs(servers)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should not send a new video notification when a video becomes unlisted', async function () {
this.timeout(50000)
const data = { privacy: VideoPrivacy.PRIVATE }
const { name, uuid, shortUUID } = await uploadRandomVideoOnServers(servers, 1, data)
await servers[0].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should not send a new video notification when a remote video becomes unlisted', async function () {
this.timeout(100000)
const data = { privacy: VideoPrivacy.PRIVATE }
const { name, uuid, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
await servers[1].videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
await waitJobs(servers)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should send a new video notification after a video import', async function () {
this.timeout(100000)
const name = 'video import ' + buildUUID()
const attributes = {
name,
channelId,
privacy: VideoPrivacy.PUBLIC,
targetUrl: FIXTURE_URLS.goodVideo
}
const { video } = await servers[0].videoImports.importVideo({ attributes })
await waitJobs(servers)
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
})
})
describe('New live from my subscription notification', function () {
let baseParams: CheckerBaseParams
async function createAndStreamLive (server: PeerTubeServer) {
const name = 'video live ' + buildUUID()
const streamDate = new Date()
const { video } = await server.live.quickCreate({ name, permanentLive: true, saveReplay: false })
await waitJobs(servers)
const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: video.uuid })
return { name, video, ffmpegCommand, streamDate }
}
before(async () => {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userAccessToken
}
await servers[0].config.enableLive({ allowReplay: false })
})
it('Should not send a notification when a live is created', async function () {
this.timeout(100000)
const name = 'video live ' + buildUUID()
const { video } = await servers[0].live.quickCreate({ name, permanentLive: true, saveReplay: false })
await waitJobs(servers)
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'absence' })
})
it('Should send a local notification when streaming in the live', async function () {
this.timeout(100000)
const { name, video, ffmpegCommand, streamDate } = await createAndStreamLive(servers[0])
await waitUntilNotification({
server: servers[0],
token: userAccessToken,
notificationType: UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION,
fromDate: streamDate
})
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
await stopFfmpeg(ffmpegCommand)
await waitJobs(servers)
})
it('Should send a remote notification when streaming in the live ', async function () {
this.timeout(100000)
const { name, video, ffmpegCommand, streamDate } = await createAndStreamLive(servers[1])
await waitUntilNotification({
server: servers[0],
token: userAccessToken,
notificationType: UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION,
fromDate: streamDate
})
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
await stopFfmpeg(ffmpegCommand)
await waitJobs(servers)
})
})
describe('My video is published', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[1],
emails,
socketNotifications: adminNotificationsServer2,
token: servers[1].accessToken
}
})
it('Should not send a notification if transcoding is not enabled', async function () {
this.timeout(50000)
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 1)
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
})
it('Should not send a notification if the wait transcoding is false', async function () {
this.timeout(240000)
await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: false })
await waitJobs(servers)
const notification = await servers[0].notifications.getLatest({ token: userAccessToken })
if (notification) {
expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
}
})
it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
this.timeout(240000)
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a notification with a transcoded video', async function () {
this.timeout(240000)
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should send a notification when an imported video is transcoded', async function () {
this.timeout(240000)
const name = 'video import ' + buildUUID()
const attributes = {
name,
channelId,
privacy: VideoPrivacy.PUBLIC,
targetUrl: FIXTURE_URLS.goodVideo,
waitTranscoding: true
}
const { video } = await servers[1].videoImports.importVideo({ attributes })
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
})
it('Should send a notification when the scheduled update has been proceeded', async function () {
this.timeout(140000)
// In 2 seconds
const updateAt = new Date(new Date().getTime() + 2000)
const data = {
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
await wait(6000)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
it('Should not send a notification before the video is published', async function () {
this.timeout(150000)
const updateAt = new Date(new Date().getTime() + 1000000)
const data = {
privacy: VideoPrivacy.PRIVATE,
scheduleUpdate: {
updateAt: updateAt.toISOString(),
privacy: VideoPrivacy.PUBLIC
}
}
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
await wait(6000)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
})
})
describe('My live replay is published', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[1],
emails,
socketNotifications: adminNotificationsServer2,
token: servers[1].accessToken
}
})
it('Should send a notification is a live replay of a non permanent live is published', async function () {
this.timeout(120000)
const { shortUUID } = await servers[1].live.create({
fields: {
name: 'non permanent live',
privacy: VideoPrivacy.PUBLIC,
channelId: servers[1].store.channel.id,
saveReplay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC },
permanentLive: false
}
})
const ffmpegCommand = await servers[1].live.sendRTMPStreamInVideo({ videoId: shortUUID })
await waitJobs(servers)
await servers[1].live.waitUntilPublished({ videoId: shortUUID })
await stopFfmpeg(ffmpegCommand)
await servers[1].live.waitUntilReplacedByReplay({ videoId: shortUUID })
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: 'non permanent live', shortUUID, checkType: 'presence' })
})
it('Should send a notification is a live replay of a permanent live is published', async function () {
this.timeout(120000)
const { shortUUID } = await servers[1].live.create({
fields: {
name: 'permanent live',
privacy: VideoPrivacy.PUBLIC,
channelId: servers[1].store.channel.id,
saveReplay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC },
permanentLive: true
}
})
const ffmpegCommand = await servers[1].live.sendRTMPStreamInVideo({ videoId: shortUUID })
await waitJobs(servers)
await servers[1].live.waitUntilPublished({ videoId: shortUUID })
const liveDetails = await servers[1].videos.get({ id: shortUUID })
await stopFfmpeg(ffmpegCommand)
await servers[1].live.waitUntilWaiting({ videoId: shortUUID })
await waitJobs(servers)
const video = await findExternalSavedVideo(servers[1], liveDetails)
expect(video).to.exist
await checkMyVideoIsPublished({ ...baseParams, videoName: video.name, shortUUID: video.shortUUID, checkType: 'presence' })
})
})
describe('Video studio', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[1],
emails,
socketNotifications: adminNotificationsServer2,
token: servers[1].accessToken
}
})
it('Should send a notification after studio edition', async function () {
this.timeout(240000)
const { name, shortUUID, id } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
await waitJobs(servers)
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
const tasks: VideoStudioTask[] = [
{
name: 'cut',
options: {
start: 0,
end: 1
}
}
]
await servers[1].videoStudio.createEditionTasks({ videoId: id, tasks })
await waitJobs(servers)
await checkVideoStudioEditionIsFinished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
})
})
describe('My video is imported', function () {
let baseParams: CheckerBaseParams
before(() => {
baseParams = {
server: servers[0],
emails,
socketNotifications: adminNotifications,
token: servers[0].accessToken
}
})
it('Should send a notification when the video import failed', async function () {
this.timeout(70000)
const name = 'video import ' + buildUUID()
const attributes = {
name,
channelId,
privacy: VideoPrivacy.PRIVATE,
targetUrl: FIXTURE_URLS.badVideo
}
const { video: { shortUUID } } = await servers[0].videoImports.importVideo({ attributes })
await waitJobs(servers)
const url = FIXTURE_URLS.badVideo
await checkMyVideoImportIsFinished({ ...baseParams, videoName: name, shortUUID, url, success: false, checkType: 'presence' })
})
it('Should send a notification when the video import succeeded', async function () {
this.timeout(70000)
const name = 'video import ' + buildUUID()
const attributes = {
name,
channelId,
privacy: VideoPrivacy.PRIVATE,
targetUrl: FIXTURE_URLS.goodVideo
}
const { video: { shortUUID } } = await servers[0].videoImports.importVideo({ attributes })
await waitJobs(servers)
const url = FIXTURE_URLS.goodVideo
await checkMyVideoImportIsFinished({ ...baseParams, videoName: name, shortUUID, url, success: true, checkType: 'presence' })
})
})
describe('New actor follow', function () {
let baseParams: CheckerBaseParams
const myChannelName = 'super channel name'
const myUserName = 'super user name'
before(async function () {
baseParams = {
server: servers[0],
emails,
socketNotifications: userNotifications,
token: userAccessToken
}
await servers[0].users.updateMe({ displayName: 'super root name' })
await servers[0].users.updateMe({
token: userAccessToken,
displayName: myUserName
})
await servers[1].users.updateMe({ displayName: 'super root 2 name' })
await servers[0].channels.update({
token: userAccessToken,
channelName: 'user_1_channel',
attributes: { displayName: myChannelName }
})
})
it('Should notify when a local channel is following one of our channel', async function () {
this.timeout(50000)
await servers[0].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
await waitJobs(servers)
await checkNewActorFollow({
...baseParams,
followType: 'channel',
followerName: 'root',
followerDisplayName: 'super root name',
followingDisplayName: myChannelName,
checkType: 'presence'
})
await servers[0].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
})
it('Should notify when a remote channel is following one of our channel', async function () {
this.timeout(50000)
await servers[1].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
await waitJobs(servers)
await checkNewActorFollow({
...baseParams,
followType: 'channel',
followerName: 'root',
followerDisplayName: 'super root 2 name',
followingDisplayName: myChannelName,
checkType: 'presence'
})
await servers[1].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
})
// PeerTube does not support account -> account follows
// it('Should notify when a local account is following one of our channel', async function () {
// this.timeout(50000)
//
// await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@' + servers[0].host)
//
// await waitJobs(servers)
//
// await checkNewActorFollow(baseParams, 'account', 'root', 'super root name', myUserName, 'presence')
// })
// it('Should notify when a remote account is following one of our channel', async function () {
// this.timeout(50000)
//
// await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@' + servers[0].host)
//
// await waitJobs(servers)
//
// await checkNewActorFollow(baseParams, 'account', 'root', 'super root 2 name', myUserName, 'presence')
// })
})
after(async function () {
MockSmtpServer.Instance.kill()
await cleanupTests(servers)
})
})
+4
ファイルの表示
@@ -0,0 +1,4 @@
export * from './live.js'
export * from './video-imports.js'
export * from './video-static-file-privacy.js'
export * from './videos.js'
+357
ファイルの表示
@@ -0,0 +1,357 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
findExternalSavedVideo,
makeRawRequest,
ObjectStorageCommand,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs,
waitUntilLivePublishedOnAllServers,
waitUntilLiveReplacedByReplayOnAllServers,
waitUntilLiveWaitingOnAllServers
} from '@peertube/peertube-server-commands'
import { expectStartWith } from '@tests/shared/checks.js'
import { testLiveVideoResolutions } from '@tests/shared/live.js'
import { MockObjectStorageProxy } from '@tests/shared/mock-servers/mock-object-storage.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
async function createLive (server: PeerTubeServer, permanent: boolean) {
const attributes: LiveVideoCreate = {
channelId: server.store.channel.id,
privacy: VideoPrivacy.PUBLIC,
name: 'my super live',
saveReplay: true,
replaySettings: { privacy: VideoPrivacy.PUBLIC },
permanentLive: permanent
}
const { uuid } = await server.live.create({ fields: attributes })
return uuid
}
async function checkFilesExist (options: {
servers: PeerTubeServer[]
videoUUID: string
numberOfFiles: number
objectStorage: ObjectStorageCommand
}) {
const { servers, videoUUID, numberOfFiles, objectStorage } = options
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(0)
expect(video.streamingPlaylists).to.have.lengthOf(1)
const files = video.streamingPlaylists[0].files
expect(files).to.have.lengthOf(numberOfFiles)
for (const file of files) {
expectStartWith(file.fileUrl, objectStorage.getMockPlaylistBaseUrl())
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
}
}
}
async function checkFilesCleanup (options: {
server: PeerTubeServer
videoUUID: string
resolutions: number[]
objectStorage: ObjectStorageCommand
}) {
const { server, videoUUID, resolutions, objectStorage } = options
const resolutionFiles = resolutions.map((_value, i) => `${i}.m3u8`)
for (const playlistName of [ 'master.m3u8' ].concat(resolutionFiles)) {
await server.live.getPlaylistFile({
videoUUID,
playlistName,
expectedStatus: HttpStatusCode.NOT_FOUND_404,
objectStorage
})
}
await server.live.getSegmentFile({
videoUUID,
playlistNumber: 0,
segment: 0,
objectStorage,
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
}
describe('Object storage for lives', function () {
if (areMockObjectStorageTestsDisabled()) return
let servers: PeerTubeServer[]
let sqlCommandServer1: SQLCommand
const objectStorage = new ObjectStorageCommand()
before(async function () {
this.timeout(120000)
await objectStorage.prepareDefaultMockBuckets()
servers = await createMultipleServers(2, objectStorage.getDefaultMockConfig())
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableTranscoding()
sqlCommandServer1 = new SQLCommand(servers[0])
})
describe('Without live transcoding', function () {
let videoUUID: string
before(async function () {
await servers[0].config.enableLive({ transcoding: false })
videoUUID = await createLive(servers[0], false)
})
it('Should create a live and publish it on object storage', async function () {
this.timeout(220000)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
await waitUntilLivePublishedOnAllServers(servers, videoUUID)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId: videoUUID,
resolutions: [ 720 ],
transcoded: false,
objectStorage
})
await stopFfmpeg(ffmpegCommand)
})
it('Should have saved the replay on object storage', async function () {
this.timeout(220000)
await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUID)
await waitJobs(servers)
await checkFilesExist({ servers, videoUUID, numberOfFiles: 1, objectStorage })
})
it('Should have cleaned up live files from object storage', async function () {
await checkFilesCleanup({ server: servers[0], videoUUID, resolutions: [ 720 ], objectStorage })
})
})
describe('With live transcoding', function () {
const resolutions = [ 720, 480, 360, 240, 144 ]
before(async function () {
await servers[0].config.enableLive({ transcoding: true })
})
describe('Normal replay', function () {
let videoUUIDNonPermanent: string
before(async function () {
videoUUIDNonPermanent = await createLive(servers[0], false)
})
it('Should create a live and publish it on object storage', async function () {
this.timeout(240000)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDNonPermanent })
await waitUntilLivePublishedOnAllServers(servers, videoUUIDNonPermanent)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId: videoUUIDNonPermanent,
resolutions,
transcoded: true,
objectStorage
})
await stopFfmpeg(ffmpegCommand)
})
it('Should have saved the replay on object storage', async function () {
this.timeout(220000)
await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUIDNonPermanent)
await waitJobs(servers)
await checkFilesExist({ servers, videoUUID: videoUUIDNonPermanent, numberOfFiles: 5, objectStorage })
})
it('Should have cleaned up live files from object storage', async function () {
await checkFilesCleanup({ server: servers[0], videoUUID: videoUUIDNonPermanent, resolutions, objectStorage })
})
})
describe('Permanent replay', function () {
let videoUUIDPermanent: string
before(async function () {
videoUUIDPermanent = await createLive(servers[0], true)
})
it('Should create a live and publish it on object storage', async function () {
this.timeout(240000)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDPermanent })
await waitUntilLivePublishedOnAllServers(servers, videoUUIDPermanent)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId: videoUUIDPermanent,
resolutions,
transcoded: true,
objectStorage
})
await stopFfmpeg(ffmpegCommand)
})
it('Should have saved the replay on object storage', async function () {
this.timeout(220000)
await waitUntilLiveWaitingOnAllServers(servers, videoUUIDPermanent)
await waitJobs(servers)
const videoLiveDetails = await servers[0].videos.get({ id: videoUUIDPermanent })
const replay = await findExternalSavedVideo(servers[0], videoLiveDetails)
await checkFilesExist({ servers, videoUUID: replay.uuid, numberOfFiles: 5, objectStorage })
})
it('Should have cleaned up live files from object storage', async function () {
await checkFilesCleanup({ server: servers[0], videoUUID: videoUUIDPermanent, resolutions, objectStorage })
})
})
})
describe('With object storage base url', function () {
const mockObjectStorageProxy = new MockObjectStorageProxy()
let baseMockUrl: string
before(async function () {
this.timeout(120000)
const port = await mockObjectStorageProxy.initialize()
const bucketName = objectStorage.getMockStreamingPlaylistsBucketName()
baseMockUrl = `http://127.0.0.1:${port}/${bucketName}`
await objectStorage.prepareDefaultMockBuckets()
const config = {
object_storage: {
enabled: true,
endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
region: ObjectStorageCommand.getMockRegion(),
credentials: ObjectStorageCommand.getMockCredentialsConfig(),
streaming_playlists: {
bucket_name: bucketName,
prefix: '',
base_url: baseMockUrl
}
}
}
await servers[0].kill()
await servers[0].run(config)
await servers[0].config.enableLive({ transcoding: true, resolutions: 'min' })
})
it('Should publish a live and replace the base url', async function () {
this.timeout(240000)
const videoUUIDPermanent = await createLive(servers[0], true)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDPermanent })
await waitUntilLivePublishedOnAllServers(servers, videoUUIDPermanent)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId: videoUUIDPermanent,
resolutions: [ 720 ],
transcoded: true,
objectStorage,
objectStorageBaseUrl: baseMockUrl
})
await stopFfmpeg(ffmpegCommand)
})
})
describe('With live stream to object storage disabled', function () {
let videoUUID: string
before(async function () {
await servers[0].kill()
await servers[0].run(objectStorage.getDefaultMockConfig({ storeLiveStreams: false }))
await servers[0].config.enableLive({ transcoding: false })
videoUUID = await createLive(servers[0], false)
})
it('Should create a live and keep it on file system', async function () {
this.timeout(220000)
const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
await waitUntilLivePublishedOnAllServers(servers, videoUUID)
await testLiveVideoResolutions({
originServer: servers[0],
sqlCommand: sqlCommandServer1,
servers,
liveVideoId: videoUUID,
resolutions: [ 720 ],
transcoded: false,
objectStorage: undefined
})
// Should not have files on object storage
await checkFilesCleanup({ server: servers[0], videoUUID, resolutions: [ 720 ], objectStorage })
await stopFfmpeg(ffmpegCommand)
})
it('Should have saved the replay on object storage', async function () {
this.timeout(220000)
await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUID)
await waitJobs(servers)
await checkFilesExist({ servers, videoUUID, numberOfFiles: 1, objectStorage })
})
})
after(async function () {
await sqlCommandServer1.cleanup()
await objectStorage.cleanupMock()
await cleanupTests(servers)
})
})
+112
ファイルの表示
@@ -0,0 +1,112 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { expectStartWith } from '@tests/shared/checks.js'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeRawRequest,
ObjectStorageCommand,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
async function importVideo (server: PeerTubeServer) {
const attributes = {
name: 'import 2',
privacy: VideoPrivacy.PUBLIC,
channelId: server.store.channel.id,
targetUrl: FIXTURE_URLS.goodVideo720
}
const { video: { uuid } } = await server.videoImports.importVideo({ attributes })
return uuid
}
describe('Object storage for video import', function () {
if (areMockObjectStorageTestsDisabled()) return
let server: PeerTubeServer
const objectStorage = new ObjectStorageCommand()
before(async function () {
this.timeout(120000)
await objectStorage.prepareDefaultMockBuckets()
server = await createSingleServer(1, objectStorage.getDefaultMockConfig())
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableVideoImports()
})
describe('Without transcoding', async function () {
before(async function () {
await server.config.disableTranscoding()
})
it('Should import a video and have sent it to object storage', async function () {
this.timeout(120000)
const uuid = await importVideo(server)
await waitJobs(server)
const video = await server.videos.get({ id: uuid })
expect(video.files).to.have.lengthOf(1)
expect(video.streamingPlaylists).to.have.lengthOf(0)
const fileUrl = video.files[0].fileUrl
expectStartWith(fileUrl, objectStorage.getMockWebVideosBaseUrl())
await makeRawRequest({ url: fileUrl, expectedStatus: HttpStatusCode.OK_200 })
})
})
describe('With transcoding', async function () {
before(async function () {
await server.config.enableTranscoding()
})
it('Should import a video and have sent it to object storage', async function () {
this.timeout(120000)
const uuid = await importVideo(server)
await waitJobs(server)
const video = await server.videos.get({ id: uuid })
expect(video.files).to.have.lengthOf(5)
expect(video.streamingPlaylists).to.have.lengthOf(1)
expect(video.streamingPlaylists[0].files).to.have.lengthOf(5)
for (const file of video.files) {
expectStartWith(file.fileUrl, objectStorage.getMockWebVideosBaseUrl())
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
}
for (const file of video.streamingPlaylists[0].files) {
expectStartWith(file.fileUrl, objectStorage.getMockPlaylistBaseUrl())
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
}
})
})
after(async function () {
await objectStorage.cleanupMock()
await cleanupTests([ server ])
})
})
+584
ファイルの表示
@@ -0,0 +1,584 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { basename } from 'path'
import { getAllFiles, getHLS } from '@peertube/peertube-core-utils'
import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
import { areScalewayObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
import {
cleanupTests,
createSingleServer,
findExternalSavedVideo,
makeRawRequest,
ObjectStorageCommand,
PeerTubeServer,
sendRTMPStream,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs
} from '@peertube/peertube-server-commands'
import { expectStartWith } from '@tests/shared/checks.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
import { checkVideoFileTokenReinjection } from '@tests/shared/streaming-playlists.js'
function extractFilenameFromUrl (url: string) {
const parts = basename(url).split(':')
return parts[parts.length - 1]
}
describe('Object storage for video static file privacy', function () {
// We need real world object storage to check ACL
if (areScalewayObjectStorageTestsDisabled()) return
let server: PeerTubeServer
let sqlCommand: SQLCommand
let userToken: string
// ---------------------------------------------------------------------------
async function checkPrivateVODFiles (uuid: string) {
const video = await server.videos.getWithToken({ id: uuid })
for (const file of video.files) {
expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/web-videos/private/')
await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
}
for (const file of getAllFiles(video)) {
const internalFileUrl = await sqlCommand.getInternalFileUrl(file.id)
expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl())
const { text } = await makeRawRequest({
url: internalFileUrl,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
expect(text).to.contain('Unsupported Authorization Type')
}
const hls = getHLS(video)
if (hls) {
for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
}
await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
for (const file of hls.files) {
expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
}
}
}
async function checkPublicVODFiles (uuid: string) {
const video = await server.videos.getWithToken({ id: uuid })
for (const file of getAllFiles(video)) {
expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl())
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
}
const hls = getHLS(video)
if (hls) {
expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl())
expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
}
}
// ---------------------------------------------------------------------------
before(async function () {
this.timeout(120000)
server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig({ serverNumber: 1 }))
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableMinimumTranscoding()
userToken = await server.users.generateUserAndToken('user1')
sqlCommand = new SQLCommand(server)
})
describe('VOD', function () {
let privateVideoUUID: string
let publicVideoUUID: string
let passwordProtectedVideoUUID: string
let userPrivateVideoUUID: string
const correctPassword = 'my super password'
const correctPasswordHeader = { 'x-peertube-video-password': correctPassword }
const incorrectPasswordHeader = { 'x-peertube-video-password': correctPassword + 'toto' }
// ---------------------------------------------------------------------------
async function getSampleFileUrls (videoId: string) {
const video = await server.videos.getWithToken({ id: videoId })
return {
webVideoFile: video.files[0].fileUrl,
hlsFile: getHLS(video).files[0].fileUrl
}
}
// ---------------------------------------------------------------------------
it('Should upload a private video and have appropriate object storage ACL', async function () {
this.timeout(120000)
{
const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
privateVideoUUID = uuid
}
{
const { uuid } = await server.videos.quickUpload({ name: 'user video', token: userToken, privacy: VideoPrivacy.PRIVATE })
userPrivateVideoUUID = uuid
}
await waitJobs([ server ])
await checkPrivateVODFiles(privateVideoUUID)
})
it('Should upload a password protected video and have appropriate object storage ACL', async function () {
this.timeout(120000)
{
const { uuid } = await server.videos.quickUpload({
name: 'video',
privacy: VideoPrivacy.PASSWORD_PROTECTED,
videoPasswords: [ correctPassword ]
})
passwordProtectedVideoUUID = uuid
}
await waitJobs([ server ])
await checkPrivateVODFiles(passwordProtectedVideoUUID)
})
it('Should upload a public video and have appropriate object storage ACL', async function () {
this.timeout(120000)
const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED })
await waitJobs([ server ])
publicVideoUUID = uuid
await checkPublicVODFiles(publicVideoUUID)
})
it('Should not get files without appropriate OAuth token', async function () {
this.timeout(60000)
const { webVideoFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
await makeRawRequest({ url: webVideoFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url: webVideoFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should not get files without appropriate password or appropriate OAuth token', async function () {
this.timeout(60000)
const { webVideoFile, hlsFile } = await getSampleFileUrls(passwordProtectedVideoUUID)
await makeRawRequest({ url: webVideoFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({
url: webVideoFile,
token: null,
headers: incorrectPasswordHeader,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
await makeRawRequest({ url: webVideoFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({
url: webVideoFile,
token: null,
headers: correctPasswordHeader,
expectedStatus: HttpStatusCode.OK_200
})
await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({
url: hlsFile,
token: null,
headers: incorrectPasswordHeader,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({
url: hlsFile,
token: null,
headers: correctPasswordHeader,
expectedStatus: HttpStatusCode.OK_200
})
})
it('Should not get HLS file of another video', async function () {
this.timeout(60000)
const privateVideo = await server.videos.getWithToken({ id: privateVideoUUID })
const hlsFilename = basename(getHLS(privateVideo).files[0].fileUrl)
const badUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + userPrivateVideoUUID + '/' + hlsFilename
const goodUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + privateVideoUUID + '/' + hlsFilename
await makeRawRequest({ url: badUrl, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
await makeRawRequest({ url: goodUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
})
it('Should correctly check OAuth, video file token of private video', async function () {
this.timeout(60000)
const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID })
const goodVideoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
const { webVideoFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
for (const url of [ webVideoFile, hlsFile ]) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should correctly check OAuth, video file token or video password of password protected video', async function () {
this.timeout(60000)
const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID })
const goodVideoFileToken = await server.videoToken.getVideoFileToken({
videoId: passwordProtectedVideoUUID,
videoPassword: correctPassword
})
const { webVideoFile, hlsFile } = await getSampleFileUrls(passwordProtectedVideoUUID)
for (const url of [ hlsFile, webVideoFile ]) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({
url,
headers: incorrectPasswordHeader,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
await makeRawRequest({ url, headers: correctPasswordHeader, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should reinject video file token', async function () {
this.timeout(120000)
const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
await checkVideoFileTokenReinjection({
server,
videoUUID: privateVideoUUID,
videoFileToken,
resolutions: [ 240, 720 ],
isLive: false
})
})
it('Should update public video to private', async function () {
this.timeout(60000)
await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } })
await checkPrivateVODFiles(publicVideoUUID)
})
it('Should update private video to public', async function () {
this.timeout(60000)
await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
await checkPublicVODFiles(publicVideoUUID)
})
})
describe('Live', function () {
let normalLiveId: string
let normalLive: LiveVideo
let permanentLiveId: string
let permanentLive: LiveVideo
let passwordProtectedLiveId: string
let passwordProtectedLive: LiveVideo
const correctPassword = 'my super password'
let unrelatedFileToken: string
// ---------------------------------------------------------------------------
async function checkLiveFiles (live: LiveVideo, liveId: string, videoPassword?: string) {
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await server.live.waitUntilPublished({ videoId: liveId })
const video = videoPassword
? await server.videos.getWithPassword({ id: liveId, password: videoPassword })
: await server.videos.getWithToken({ id: liveId })
const fileToken = videoPassword
? await server.videoToken.getVideoFileToken({ token: null, videoId: video.uuid, videoPassword })
: await server.videoToken.getVideoFileToken({ videoId: video.uuid })
const hls = video.streamingPlaylists[0]
for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
if (videoPassword) {
await makeRawRequest({
url,
headers: { 'x-peertube-video-password': videoPassword },
expectedStatus: HttpStatusCode.OK_200
})
await makeRawRequest({
url,
headers: { 'x-peertube-video-password': 'incorrectPassword' },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
}
await stopFfmpeg(ffmpegCommand)
}
async function checkReplay (replay: VideoDetails) {
const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
const hls = replay.streamingPlaylists[0]
expect(hls.files).to.not.have.lengthOf(0)
for (const file of hls.files) {
await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({
url: file.fileUrl,
query: { videoFileToken: unrelatedFileToken },
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
}
}
// ---------------------------------------------------------------------------
before(async function () {
await server.config.enableMinimumTranscoding()
const { uuid } = await server.videos.quickUpload({ name: 'another video' })
unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
await server.config.enableLive({
allowReplay: true,
transcoding: true,
resolutions: 'min'
})
{
const { video, live } = await server.live.quickCreate({
saveReplay: true,
permanentLive: false,
privacy: VideoPrivacy.PRIVATE
})
normalLiveId = video.uuid
normalLive = live
}
{
const { video, live } = await server.live.quickCreate({
saveReplay: true,
permanentLive: true,
privacy: VideoPrivacy.PRIVATE
})
permanentLiveId = video.uuid
permanentLive = live
}
{
const { video, live } = await server.live.quickCreate({
saveReplay: false,
permanentLive: false,
privacy: VideoPrivacy.PASSWORD_PROTECTED,
videoPasswords: [ correctPassword ]
})
passwordProtectedLiveId = video.uuid
passwordProtectedLive = live
}
})
it('Should create a private normal live and have a private static path', async function () {
this.timeout(240000)
await checkLiveFiles(normalLive, normalLiveId)
})
it('Should create a private permanent live and have a private static path', async function () {
this.timeout(240000)
await checkLiveFiles(permanentLive, permanentLiveId)
})
it('Should create a password protected live and have a private static path', async function () {
this.timeout(240000)
await checkLiveFiles(passwordProtectedLive, passwordProtectedLiveId, correctPassword)
})
it('Should reinject video file token in permanent live', async function () {
this.timeout(240000)
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: permanentLive.rtmpUrl, streamKey: permanentLive.streamKey })
await server.live.waitUntilPublished({ videoId: permanentLiveId })
const video = await server.videos.getWithToken({ id: permanentLiveId })
const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
await checkVideoFileTokenReinjection({
server,
videoUUID: permanentLiveId,
videoFileToken,
resolutions: [ 720 ],
isLive: true
})
await stopFfmpeg(ffmpegCommand)
})
it('Should have created a replay of the normal live with a private static path', async function () {
this.timeout(240000)
await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
const replay = await server.videos.getWithToken({ id: normalLiveId })
await checkReplay(replay)
})
it('Should have created a replay of the permanent live with a private static path', async function () {
this.timeout(240000)
await server.live.waitUntilWaiting({ videoId: permanentLiveId })
await waitJobs([ server ])
const live = await server.videos.getWithToken({ id: permanentLiveId })
const replayFromList = await findExternalSavedVideo(server, live)
const replay = await server.videos.getWithToken({ id: replayFromList.id })
await checkReplay(replay)
})
})
describe('With private files proxy disabled and public ACL for private files', function () {
let videoUUID: string
before(async function () {
this.timeout(240000)
await server.kill()
const config = ObjectStorageCommand.getDefaultScalewayConfig({
serverNumber: 1,
enablePrivateProxy: false,
privateACL: 'public-read'
})
await server.run(config)
const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
videoUUID = uuid
await waitJobs([ server ])
})
it('Should display object storage path for a private video and be able to access them', async function () {
this.timeout(60000)
await checkPublicVODFiles(videoUUID)
})
it('Should not be able to access object storage proxy', async function () {
const privateVideo = await server.videos.getWithToken({ id: videoUUID })
const webVideoFilename = extractFilenameFromUrl(privateVideo.files[0].fileUrl)
const hlsFilename = extractFilenameFromUrl(getHLS(privateVideo).files[0].fileUrl)
await makeRawRequest({
url: server.url + '/object-storage-proxy/web-videos/private/' + webVideoFilename,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
await makeRawRequest({
url: server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + videoUUID + '/' + hlsFilename,
token: server.accessToken,
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
})
after(async function () {
this.timeout(240000)
const { data } = await server.videos.listAllForAdmin()
for (const v of data) {
await server.videos.remove({ id: v.uuid })
}
for (const v of data) {
await server.servers.waitUntilLog('Removed files of video ' + v.url)
}
await sqlCommand.cleanup()
await cleanupTests([ server ])
})
})
+434
ファイルの表示
@@ -0,0 +1,434 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import bytes from 'bytes'
import { expect } from 'chai'
import { stat } from 'fs/promises'
import merge from 'lodash-es/merge.js'
import { HttpStatusCode, VideoDetails } from '@peertube/peertube-models'
import { areMockObjectStorageTestsDisabled, sha1 } from '@peertube/peertube-node-utils'
import {
cleanupTests,
createMultipleServers,
createSingleServer,
doubleFollow,
killallServers,
makeRawRequest,
ObjectStorageCommand,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { expectStartWith, expectLogDoesNotContain } from '@tests/shared/checks.js'
import { checkTmpIsEmpty } from '@tests/shared/directories.js'
import { generateHighBitrateVideo } from '@tests/shared/generate.js'
import { MockObjectStorageProxy } from '@tests/shared/mock-servers/mock-object-storage.js'
import { SQLCommand } from '@tests/shared/sql-command.js'
import { checkWebTorrentWorks } from '@tests/shared/webtorrent.js'
async function checkFiles (options: {
server: PeerTubeServer
originServer: PeerTubeServer
originSQLCommand: SQLCommand
video: VideoDetails
baseMockUrl?: string
playlistBucket: string
playlistPrefix?: string
webVideoBucket: string
webVideoPrefix?: string
}) {
const {
server,
originServer,
originSQLCommand,
video,
playlistBucket,
webVideoBucket,
baseMockUrl,
playlistPrefix,
webVideoPrefix
} = options
let allFiles = video.files
for (const file of video.files) {
const baseUrl = baseMockUrl
? `${baseMockUrl}/${webVideoBucket}/`
: `http://${webVideoBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
const prefix = webVideoPrefix || ''
const start = baseUrl + prefix
expectStartWith(file.fileUrl, start)
const res = await makeRawRequest({ url: file.fileDownloadUrl, expectedStatus: HttpStatusCode.FOUND_302 })
const location = res.headers['location']
expectStartWith(location, start)
await makeRawRequest({ url: location, expectedStatus: HttpStatusCode.OK_200 })
}
const hls = video.streamingPlaylists[0]
if (hls) {
allFiles = allFiles.concat(hls.files)
const baseUrl = baseMockUrl
? `${baseMockUrl}/${playlistBucket}/`
: `http://${playlistBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
const prefix = playlistPrefix || ''
const start = baseUrl + prefix
expectStartWith(hls.playlistUrl, start)
expectStartWith(hls.segmentsSha256Url, start)
await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
const resSha = await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
expect(JSON.stringify(resSha.body)).to.not.throw
let i = 0
for (const file of hls.files) {
expectStartWith(file.fileUrl, start)
const res = await makeRawRequest({ url: file.fileDownloadUrl, expectedStatus: HttpStatusCode.FOUND_302 })
const location = res.headers['location']
expectStartWith(location, start)
await makeRawRequest({ url: location, expectedStatus: HttpStatusCode.OK_200 })
if (originServer.internalServerNumber === server.internalServerNumber) {
const infohash = sha1(`${2 + hls.playlistUrl}+V${i}`)
const dbInfohashes = await originSQLCommand.getPlaylistInfohash(hls.id)
expect(dbInfohashes).to.include(infohash)
}
i++
}
}
for (const file of allFiles) {
await checkWebTorrentWorks(file.magnetUri)
const res = await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(res.body).to.have.length.above(100)
}
return allFiles.map(f => f.fileUrl)
}
function runTestSuite (options: {
fixture?: string
maxUploadPart?: string
playlistBucket: string
playlistPrefix?: string
webVideoBucket: string
webVideoPrefix?: string
useMockBaseUrl?: boolean
}) {
const mockObjectStorageProxy = new MockObjectStorageProxy()
const { fixture } = options
let baseMockUrl: string
let servers: PeerTubeServer[]
let sqlCommands: SQLCommand[] = []
const objectStorage = new ObjectStorageCommand()
let keptUrls: string[] = []
const uuidsToDelete: string[] = []
let deletedUrls: string[] = []
before(async function () {
this.timeout(240000)
const port = await mockObjectStorageProxy.initialize()
baseMockUrl = options.useMockBaseUrl
? `http://127.0.0.1:${port}`
: undefined
await objectStorage.createMockBucket(options.playlistBucket)
await objectStorage.createMockBucket(options.webVideoBucket)
const config = {
object_storage: {
enabled: true,
endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
region: ObjectStorageCommand.getMockRegion(),
credentials: ObjectStorageCommand.getMockCredentialsConfig(),
max_upload_part: options.maxUploadPart || '5MB',
streaming_playlists: {
bucket_name: options.playlistBucket,
prefix: options.playlistPrefix,
base_url: baseMockUrl
? `${baseMockUrl}/${options.playlistBucket}`
: undefined
},
web_videos: {
bucket_name: options.webVideoBucket,
prefix: options.webVideoPrefix,
base_url: baseMockUrl
? `${baseMockUrl}/${options.webVideoBucket}`
: undefined
}
}
}
servers = await createMultipleServers(2, config)
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
for (const server of servers) {
const { uuid } = await server.videos.quickUpload({ name: 'video to keep' })
await waitJobs(servers)
const files = await server.videos.listFiles({ id: uuid })
keptUrls = keptUrls.concat(files.map(f => f.fileUrl))
}
sqlCommands = servers.map(s => new SQLCommand(s))
})
it('Should upload a video and move it to the object storage without transcoding', async function () {
this.timeout(40000)
const { uuid } = await servers[0].videos.quickUpload({ name: 'video 1', fixture })
uuidsToDelete.push(uuid)
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: uuid })
const files = await checkFiles({ ...options, server, originServer: servers[0], originSQLCommand: sqlCommands[0], video, baseMockUrl })
deletedUrls = deletedUrls.concat(files)
}
})
it('Should upload a video and move it to the object storage with transcoding', async function () {
this.timeout(120000)
const { uuid } = await servers[1].videos.quickUpload({ name: 'video 2', fixture })
uuidsToDelete.push(uuid)
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: uuid })
const files = await checkFiles({ ...options, server, originServer: servers[0], originSQLCommand: sqlCommands[0], video, baseMockUrl })
deletedUrls = deletedUrls.concat(files)
}
})
it('Should fetch correctly all the files', async function () {
for (const url of deletedUrls.concat(keptUrls)) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should correctly delete the files', async function () {
await servers[0].videos.remove({ id: uuidsToDelete[0] })
await servers[1].videos.remove({ id: uuidsToDelete[1] })
await waitJobs(servers)
for (const url of deletedUrls) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
}
})
it('Should have kept other files', async function () {
for (const url of keptUrls) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
}
})
it('Should have an empty tmp directory', async function () {
for (const server of servers) {
await checkTmpIsEmpty(server)
}
})
it('Should not have downloaded files from object storage', async function () {
for (const server of servers) {
await expectLogDoesNotContain(server, 'from object storage')
}
})
after(async function () {
await mockObjectStorageProxy.terminate()
await objectStorage.cleanupMock()
for (const sqlCommand of sqlCommands) {
await sqlCommand.cleanup()
}
await cleanupTests(servers)
})
}
describe('Object storage for videos', function () {
if (areMockObjectStorageTestsDisabled()) return
const objectStorage = new ObjectStorageCommand()
describe('Test config', function () {
let server: PeerTubeServer
const baseConfig = objectStorage.getDefaultMockConfig()
const badCredentials = {
access_key_id: 'AKIAIOSFODNN7EXAMPLE',
secret_access_key: 'aJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
}
it('Should fail with same bucket names without prefix', function (done) {
const config = merge({}, baseConfig, {
object_storage: {
streaming_playlists: {
bucket_name: 'aaa'
},
web_videos: {
bucket_name: 'aaa'
}
}
})
createSingleServer(1, config)
.then(() => done(new Error('Did not throw')))
.catch(() => done())
})
it('Should fail with bad credentials', async function () {
this.timeout(60000)
await objectStorage.prepareDefaultMockBuckets()
const config = merge({}, baseConfig, {
object_storage: {
credentials: badCredentials
}
})
server = await createSingleServer(1, config)
await setAccessTokensToServers([ server ])
const { uuid } = await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ], { skipDelayed: true })
const video = await server.videos.get({ id: uuid })
expectStartWith(video.files[0].fileUrl, server.url)
await killallServers([ server ])
})
it('Should succeed with credentials from env', async function () {
this.timeout(60000)
await objectStorage.prepareDefaultMockBuckets()
const config = merge({}, baseConfig, {
object_storage: {
credentials: {
access_key_id: '',
secret_access_key: ''
}
}
})
const goodCredentials = ObjectStorageCommand.getMockCredentialsConfig()
server = await createSingleServer(1, config, {
env: {
AWS_ACCESS_KEY_ID: goodCredentials.access_key_id,
AWS_SECRET_ACCESS_KEY: goodCredentials.secret_access_key
}
})
await setAccessTokensToServers([ server ])
const { uuid } = await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ], { skipDelayed: true })
const video = await server.videos.get({ id: uuid })
expectStartWith(video.files[0].fileUrl, objectStorage.getMockWebVideosBaseUrl())
})
after(async function () {
await objectStorage.cleanupMock()
await cleanupTests([ server ])
})
})
describe('Test simple object storage', function () {
runTestSuite({
playlistBucket: objectStorage.getMockBucketName('streaming-playlists'),
webVideoBucket: objectStorage.getMockBucketName('web-videos')
})
})
describe('Test object storage with prefix', function () {
runTestSuite({
playlistBucket: objectStorage.getMockBucketName('mybucket'),
webVideoBucket: objectStorage.getMockBucketName('mybucket'),
playlistPrefix: 'streaming-playlists_',
webVideoPrefix: 'webvideo_'
})
})
describe('Test object storage with prefix and base URL', function () {
runTestSuite({
playlistBucket: objectStorage.getMockBucketName('mybucket'),
webVideoBucket: objectStorage.getMockBucketName('mybucket'),
playlistPrefix: 'streaming-playlists/',
webVideoPrefix: 'webvideo/',
useMockBaseUrl: true
})
})
describe('Test object storage with file bigger than upload part', function () {
let fixture: string
const maxUploadPart = '5MB'
before(async function () {
this.timeout(120000)
fixture = await generateHighBitrateVideo()
const { size } = await stat(fixture)
if (bytes.parse(maxUploadPart) > size) {
throw Error(`Fixture file is too small (${size}) to make sense for this test.`)
}
})
runTestSuite({
maxUploadPart,
playlistBucket: objectStorage.getMockBucketName('streaming-playlists'),
webVideoBucket: objectStorage.getMockBucketName('web-videos'),
fixture
})
})
})
+3
ファイルの表示
@@ -0,0 +1,3 @@
import './redundancy-constraints.js'
import './redundancy.js'
import './manage-redundancy.js'
+324
ファイルの表示
@@ -0,0 +1,324 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
RedundancyCommand,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { VideoPrivacy, VideoRedundanciesTarget } from '@peertube/peertube-models'
describe('Test manage videos redundancy', function () {
const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ]
let servers: PeerTubeServer[]
let video1Server2UUID: string
let video2Server2UUID: string
let redundanciesToRemove: number[] = []
let commands: RedundancyCommand[]
before(async function () {
this.timeout(120000)
const config = {
transcoding: {
hls: {
enabled: true
}
},
redundancy: {
videos: {
check_interval: '1 second',
strategies: [
{
strategy: 'recently-added',
min_lifetime: '1 hour',
size: '10MB',
min_views: 0
}
]
}
}
}
servers = await createMultipleServers(3, config)
// Get the access tokens
await setAccessTokensToServers(servers)
commands = servers.map(s => s.redundancy)
{
const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video 1 server 2' } })
video1Server2UUID = uuid
}
{
const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video 2 server 2' } })
video2Server2UUID = uuid
}
await waitJobs(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
await commands[0].updateRedundancy({ host: servers[1].host, redundancyAllowed: true })
await waitJobs(servers)
})
it('Should not have redundancies on server 3', async function () {
for (const target of targets) {
const body = await commands[2].listVideos({ target })
expect(body.total).to.equal(0)
expect(body.data).to.have.lengthOf(0)
}
})
it('Should correctly list followings by redundancy', async function () {
const body = await servers[0].follows.getFollowings({ sort: '-redundancyAllowed' })
expect(body.total).to.equal(2)
expect(body.data).to.have.lengthOf(2)
expect(body.data[0].following.host).to.equal(servers[1].host)
expect(body.data[1].following.host).to.equal(servers[2].host)
})
it('Should not have "remote-videos" redundancies on server 2', async function () {
this.timeout(120000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 10)
await waitJobs(servers)
const body = await commands[1].listVideos({ target: 'remote-videos' })
expect(body.total).to.equal(0)
expect(body.data).to.have.lengthOf(0)
})
it('Should have "my-videos" redundancies on server 2', async function () {
this.timeout(120000)
const body = await commands[1].listVideos({ target: 'my-videos' })
expect(body.total).to.equal(2)
const videos = body.data
expect(videos).to.have.lengthOf(2)
const videos1 = videos.find(v => v.uuid === video1Server2UUID)
const videos2 = videos.find(v => v.uuid === video2Server2UUID)
expect(videos1.name).to.equal('video 1 server 2')
expect(videos2.name).to.equal('video 2 server 2')
expect(videos1.redundancies.files).to.have.lengthOf(4)
expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
for (const r of redundancies) {
expect(r.strategy).to.be.null
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
expect(r.updatedAt).to.exist
expect(r.expiresOn).to.exist
}
})
it('Should not have "my-videos" redundancies on server 1', async function () {
const body = await commands[0].listVideos({ target: 'my-videos' })
expect(body.total).to.equal(0)
expect(body.data).to.have.lengthOf(0)
})
it('Should have "remote-videos" redundancies on server 1', async function () {
this.timeout(120000)
const body = await commands[0].listVideos({ target: 'remote-videos' })
expect(body.total).to.equal(2)
const videos = body.data
expect(videos).to.have.lengthOf(2)
const videos1 = videos.find(v => v.uuid === video1Server2UUID)
const videos2 = videos.find(v => v.uuid === video2Server2UUID)
expect(videos1.name).to.equal('video 1 server 2')
expect(videos2.name).to.equal('video 2 server 2')
expect(videos1.redundancies.files).to.have.lengthOf(4)
expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
for (const r of redundancies) {
expect(r.strategy).to.equal('recently-added')
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
expect(r.updatedAt).to.exist
expect(r.expiresOn).to.exist
}
})
it('Should correctly paginate and sort results', async function () {
{
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: 'name',
start: 0,
count: 2
})
const videos = body.data
expect(videos[0].name).to.equal('video 1 server 2')
expect(videos[1].name).to.equal('video 2 server 2')
}
{
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: '-name',
start: 0,
count: 2
})
const videos = body.data
expect(videos[0].name).to.equal('video 2 server 2')
expect(videos[1].name).to.equal('video 1 server 2')
}
{
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: '-name',
start: 1,
count: 1
})
expect(body.data[0].name).to.equal('video 1 server 2')
}
})
it('Should manually add a redundancy and list it', async function () {
this.timeout(120000)
const uuid = (await servers[1].videos.quickUpload({ name: 'video 3 server 2', privacy: VideoPrivacy.UNLISTED })).uuid
await waitJobs(servers)
const videoId = await servers[0].videos.getId({ uuid })
await commands[0].addVideo({ videoId })
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 15)
await waitJobs(servers)
{
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: '-name',
start: 0,
count: 5
})
const video = body.data[0]
expect(video.name).to.equal('video 3 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
for (const r of redundancies) {
redundanciesToRemove.push(r.id)
expect(r.strategy).to.equal('manual')
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
expect(r.updatedAt).to.exist
expect(r.expiresOn).to.be.null
}
}
const body = await commands[1].listVideos({
target: 'my-videos',
sort: '-name',
start: 0,
count: 5
})
const video = body.data[0]
expect(video.name).to.equal('video 3 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
for (const r of redundancies) {
expect(r.strategy).to.be.null
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
expect(r.updatedAt).to.exist
expect(r.expiresOn).to.be.null
}
})
it('Should manually remove a redundancy and remove it from the list', async function () {
this.timeout(120000)
for (const redundancyId of redundanciesToRemove) {
await commands[0].removeVideo({ redundancyId })
}
{
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: '-name',
start: 0,
count: 5
})
const videos = body.data
expect(videos).to.have.lengthOf(2)
const video = videos[0]
expect(video.name).to.equal('video 2 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
redundanciesToRemove = redundancies.map(r => r.id)
}
})
it('Should remove another (auto) redundancy', async function () {
for (const redundancyId of redundanciesToRemove) {
await commands[0].removeVideo({ redundancyId })
}
const body = await commands[0].listVideos({
target: 'remote-videos',
sort: '-name',
start: 0,
count: 5
})
const videos = body.data
expect(videos).to.have.lengthOf(1)
expect(videos[0].name).to.equal('video 1 server 2')
})
after(async function () {
await cleanupTests(servers)
})
})
+191
ファイルの表示
@@ -0,0 +1,191 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
killallServers,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test redundancy constraints', function () {
let remoteServer: PeerTubeServer
let localServer: PeerTubeServer
let servers: PeerTubeServer[]
const remoteServerConfig = {
redundancy: {
videos: {
check_interval: '1 second',
strategies: [
{
strategy: 'recently-added',
min_lifetime: '1 hour',
size: '100MB',
min_views: 0
}
]
}
}
}
async function uploadWrapper (videoName: string) {
// Wait for transcoding
const { id } = await localServer.videos.upload({ attributes: { name: 'to transcode', privacy: VideoPrivacy.PRIVATE } })
await waitJobs([ localServer ])
// Update video to schedule a federation
await localServer.videos.update({ id, attributes: { name: videoName, privacy: VideoPrivacy.PUBLIC } })
}
async function getTotalRedundanciesLocalServer () {
const body = await localServer.redundancy.listVideos({ target: 'my-videos' })
return body.total
}
async function getTotalRedundanciesRemoteServer () {
const body = await remoteServer.redundancy.listVideos({ target: 'remote-videos' })
return body.total
}
before(async function () {
this.timeout(120000)
{
remoteServer = await createSingleServer(1, remoteServerConfig)
}
{
const config = {
remote_redundancy: {
videos: {
accept_from: 'nobody'
}
}
}
localServer = await createSingleServer(2, config)
}
servers = [ remoteServer, localServer ]
// Get the access tokens
await setAccessTokensToServers(servers)
await localServer.videos.upload({ attributes: { name: 'video 1 server 2' } })
await waitJobs(servers)
// Server 1 and server 2 follow each other
await remoteServer.follows.follow({ hosts: [ localServer.url ] })
await waitJobs(servers)
await remoteServer.redundancy.updateRedundancy({ host: localServer.host, redundancyAllowed: true })
await waitJobs(servers)
})
it('Should have redundancy on server 1 but not on server 2 with a nobody filter', async function () {
this.timeout(120000)
await waitJobs(servers)
await remoteServer.servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
{
const total = await getTotalRedundanciesRemoteServer()
expect(total).to.equal(1)
}
{
const total = await getTotalRedundanciesLocalServer()
expect(total).to.equal(0)
}
})
it('Should have redundancy on server 1 and on server 2 with an anybody filter', async function () {
this.timeout(120000)
const config = {
remote_redundancy: {
videos: {
accept_from: 'anybody'
}
}
}
await killallServers([ localServer ])
await localServer.run(config)
await uploadWrapper('video 2 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 10)
await waitJobs(servers)
{
const total = await getTotalRedundanciesRemoteServer()
expect(total).to.equal(2)
}
{
const total = await getTotalRedundanciesLocalServer()
expect(total).to.equal(1)
}
})
it('Should have redundancy on server 1 but not on server 2 with a followings filter', async function () {
this.timeout(120000)
const config = {
remote_redundancy: {
videos: {
accept_from: 'followings'
}
}
}
await killallServers([ localServer ])
await localServer.run(config)
await uploadWrapper('video 3 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 15)
await waitJobs(servers)
{
const total = await getTotalRedundanciesRemoteServer()
expect(total).to.equal(3)
}
{
const total = await getTotalRedundanciesLocalServer()
expect(total).to.equal(1)
}
})
it('Should have redundancy on server 1 and on server 2 with followings filter now server 2 follows server 1', async function () {
this.timeout(120000)
await localServer.follows.follow({ hosts: [ remoteServer.url ] })
await waitJobs(servers)
await uploadWrapper('video 4 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 20)
await waitJobs(servers)
{
const total = await getTotalRedundanciesRemoteServer()
expect(total).to.equal(4)
}
{
const total = await getTotalRedundanciesLocalServer()
expect(total).to.equal(2)
}
})
after(async function () {
await cleanupTests(servers)
})
})
+743
ファイルの表示
@@ -0,0 +1,743 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { readdir } from 'fs/promises'
import { basename, join } from 'path'
import { wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
VideoDetails,
VideoFile,
VideoPrivacy,
VideoRedundancyStrategy,
VideoRedundancyStrategyWithManual
} from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
killallServers,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkSegmentHash } from '@tests/shared/streaming-playlists.js'
import { checkVideoFilesWereRemoved, saveVideoInServers } from '@tests/shared/videos.js'
import { magnetUriDecode } from '@tests/shared/webtorrent.js'
let servers: PeerTubeServer[] = []
let video1Server2: VideoDetails
async function checkMagnetWebseeds (file: VideoFile, baseWebseeds: string[], server: PeerTubeServer) {
const parsed = await magnetUriDecode(file.magnetUri)
for (const ws of baseWebseeds) {
const found = parsed.urlList.find(url => url === `${ws}${basename(file.fileUrl)}`)
expect(found, `Webseed ${ws} not found in ${file.magnetUri} on server ${server.url}`).to.not.be.undefined
}
expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
for (const url of parsed.urlList) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
}
}
async function createServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}, withWebVideo = true) {
const strategies: any[] = []
if (strategy !== null) {
strategies.push(
{
min_lifetime: '1 hour',
strategy,
size: '400KB',
...additionalParams
}
)
}
const config = {
transcoding: {
web_videos: {
enabled: withWebVideo
},
hls: {
enabled: true
}
},
redundancy: {
videos: {
check_interval: '5 seconds',
strategies
}
}
}
servers = await createMultipleServers(3, config)
// Get the access tokens
await setAccessTokensToServers(servers)
{
const { id } = await servers[1].videos.upload({ attributes: { name: 'video 1 server 2' } })
video1Server2 = await servers[1].videos.get({ id })
await servers[1].views.simulateView({ id })
}
await waitJobs(servers)
// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
// Server 1 and server 3 follow each other
await doubleFollow(servers[0], servers[2])
// Server 2 and server 3 follow each other
await doubleFollow(servers[1], servers[2])
await waitJobs(servers)
}
async function ensureSameFilenames (videoUUID: string) {
let webVideoFilenames: string[]
let hlsFilenames: string[]
for (const server of servers) {
const video = await server.videos.getWithToken({ id: videoUUID })
// Ensure we use the same filenames that the origin
const localWebVideoFilenames = video.files.map(f => basename(f.fileUrl)).sort()
const localHLSFilenames = video.streamingPlaylists[0].files.map(f => basename(f.fileUrl)).sort()
if (webVideoFilenames) expect(webVideoFilenames).to.deep.equal(localWebVideoFilenames)
else webVideoFilenames = localWebVideoFilenames
if (hlsFilenames) expect(hlsFilenames).to.deep.equal(localHLSFilenames)
else hlsFilenames = localHLSFilenames
}
return { webVideoFilenames, hlsFilenames }
}
async function check1WebSeed (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
const webseeds = [
`${servers[1].url}/static/web-videos/`
]
for (const server of servers) {
// With token to avoid issues with video follow constraints
const video = await server.videos.getWithToken({ id: videoUUID })
for (const f of video.files) {
await checkMagnetWebseeds(f, webseeds, server)
}
}
await ensureSameFilenames(videoUUID)
}
async function check2Webseeds (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
const webseeds = [
`${servers[0].url}/static/redundancy/`,
`${servers[1].url}/static/web-videos/`
]
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
for (const file of video.files) {
await checkMagnetWebseeds(file, webseeds, server)
}
}
const { webVideoFilenames } = await ensureSameFilenames(videoUUID)
const directories = [
servers[0].getDirectoryPath('redundancy'),
servers[1].getDirectoryPath('web-videos')
]
for (const directory of directories) {
const files = await readdir(directory)
expect(files).to.have.length.at.least(4)
// Ensure we files exist on disk
expect(files.find(f => webVideoFilenames.includes(f))).to.exist
}
}
async function check0PlaylistRedundancies (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
for (const server of servers) {
// With token to avoid issues with video follow constraints
const video = await server.videos.getWithToken({ id: videoUUID })
expect(video.streamingPlaylists).to.be.an('array')
expect(video.streamingPlaylists).to.have.lengthOf(1)
expect(video.streamingPlaylists[0].redundancies).to.have.lengthOf(0)
}
await ensureSameFilenames(videoUUID)
}
async function check1PlaylistRedundancies (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.streamingPlaylists).to.have.lengthOf(1)
expect(video.streamingPlaylists[0].redundancies).to.have.lengthOf(1)
const redundancy = video.streamingPlaylists[0].redundancies[0]
expect(redundancy.baseUrl).to.equal(servers[0].url + '/static/redundancy/hls/' + videoUUID)
}
const baseUrlPlaylist = servers[1].url + '/static/streaming-playlists/hls/' + videoUUID
const baseUrlSegment = servers[0].url + '/static/redundancy/hls/' + videoUUID
const video = await servers[0].videos.get({ id: videoUUID })
const hlsPlaylist = video.streamingPlaylists[0]
for (const resolution of [ 240, 360, 480, 720 ]) {
await checkSegmentHash({ server: servers[1], baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist })
}
const { hlsFilenames } = await ensureSameFilenames(videoUUID)
const directories = [
servers[0].getDirectoryPath('redundancy/hls'),
servers[1].getDirectoryPath('streaming-playlists/hls')
]
for (const directory of directories) {
const files = await readdir(join(directory, videoUUID))
expect(files).to.have.length.at.least(4)
// Ensure we files exist on disk
expect(files.find(f => hlsFilenames.includes(f))).to.exist
}
}
async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) {
let totalSize: number = null
let statsLength = 1
if (strategy !== 'manual') {
totalSize = 409600
statsLength = 2
}
const data = await servers[0].stats.get()
expect(data.videosRedundancy).to.have.lengthOf(statsLength)
const stat = data.videosRedundancy[0]
expect(stat.strategy).to.equal(strategy)
expect(stat.totalSize).to.equal(totalSize)
return stat
}
async function checkStatsWith1Redundancy (strategy: VideoRedundancyStrategyWithManual, onlyHls = false) {
const stat = await checkStatsGlobal(strategy)
expect(stat.totalUsed).to.be.at.least(1).and.below(409601)
expect(stat.totalVideoFiles).to.equal(onlyHls ? 4 : 8)
expect(stat.totalVideos).to.equal(1)
}
async function checkStatsWithoutRedundancy (strategy: VideoRedundancyStrategyWithManual) {
const stat = await checkStatsGlobal(strategy)
expect(stat.totalUsed).to.equal(0)
expect(stat.totalVideoFiles).to.equal(0)
expect(stat.totalVideos).to.equal(0)
}
async function findServerFollows () {
const body = await servers[0].follows.getFollowings({ start: 0, count: 5, sort: '-createdAt' })
const follows = body.data
const server2 = follows.find(f => f.following.host === `${servers[1].host}`)
const server3 = follows.find(f => f.following.host === `${servers[2].host}`)
return { server2, server3 }
}
async function enableRedundancyOnServer1 () {
await servers[0].redundancy.updateRedundancy({ host: servers[1].host, redundancyAllowed: true })
const { server2, server3 } = await findServerFollows()
expect(server3).to.not.be.undefined
expect(server3.following.hostRedundancyAllowed).to.be.false
expect(server2).to.not.be.undefined
expect(server2.following.hostRedundancyAllowed).to.be.true
}
async function disableRedundancyOnServer1 () {
await servers[0].redundancy.updateRedundancy({ host: servers[1].host, redundancyAllowed: false })
const { server2, server3 } = await findServerFollows()
expect(server3).to.not.be.undefined
expect(server3.following.hostRedundancyAllowed).to.be.false
expect(server2).to.not.be.undefined
expect(server2.following.hostRedundancyAllowed).to.be.false
}
describe('Test videos redundancy', function () {
describe('With most-views strategy', function () {
const strategy = 'most-views'
before(function () {
this.timeout(240000)
return createServers(strategy)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
it('Should enable redundancy on server 1', function () {
return enableRedundancyOnServer1()
})
it('Should have 2 webseeds on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
it('Should undo redundancy on server 1 and remove duplicated videos', async function () {
this.timeout(80000)
await disableRedundancyOnServer1()
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
})
after(async function () {
return cleanupTests(servers)
})
})
describe('With trending strategy', function () {
const strategy = 'trending'
before(function () {
this.timeout(240000)
return createServers(strategy)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
it('Should enable redundancy on server 1', function () {
return enableRedundancyOnServer1()
})
it('Should have 2 webseeds on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
it('Should unfollow server 3 and keep duplicated videos', async function () {
this.timeout(80000)
await servers[0].follows.unfollow({ target: servers[2] })
await waitJobs(servers)
await wait(5000)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
it('Should unfollow server 2 and remove duplicated videos', async function () {
this.timeout(80000)
await servers[0].follows.unfollow({ target: servers[1] })
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
})
after(async function () {
await cleanupTests(servers)
})
})
describe('With recently added strategy', function () {
const strategy = 'recently-added'
before(function () {
this.timeout(240000)
return createServers(strategy, { min_views: 3 })
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
it('Should enable redundancy on server 1', function () {
return enableRedundancyOnServer1()
})
it('Should still have 1 webseed on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await wait(15000)
await waitJobs(servers)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
it('Should view 2 times the first video to have > min_views config', async function () {
this.timeout(80000)
await servers[0].views.simulateView({ id: video1Server2.uuid })
await servers[2].views.simulateView({ id: video1Server2.uuid })
await wait(10000)
await waitJobs(servers)
})
it('Should have 2 webseeds on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
it('Should remove the video and the redundancy files', async function () {
this.timeout(20000)
await saveVideoInServers(servers, video1Server2.uuid)
await servers[1].videos.remove({ id: video1Server2.uuid })
await waitJobs(servers)
for (const server of servers) {
await checkVideoFilesWereRemoved({ server, video: server.store.videoDetails })
}
})
after(async function () {
await cleanupTests(servers)
})
})
describe('With only HLS files', function () {
const strategy = 'recently-added'
before(async function () {
this.timeout(240000)
await createServers(strategy, { min_views: 3 }, false)
})
it('Should have 0 playlist redundancy on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
})
it('Should enable redundancy on server 1', function () {
return enableRedundancyOnServer1()
})
it('Should still have 0 redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await wait(15000)
await waitJobs(servers)
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
it('Should have 1 redundancy on the first video', async function () {
this.timeout(160000)
await servers[0].views.simulateView({ id: video1Server2.uuid })
await servers[2].views.simulateView({ id: video1Server2.uuid })
await wait(10000)
await waitJobs(servers)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 1)
await waitJobs(servers)
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy, true)
})
it('Should remove the video and the redundancy files', async function () {
this.timeout(20000)
await saveVideoInServers(servers, video1Server2.uuid)
await servers[1].videos.remove({ id: video1Server2.uuid })
await waitJobs(servers)
for (const server of servers) {
await checkVideoFilesWereRemoved({ server, video: server.store.videoDetails })
}
})
after(async function () {
await cleanupTests(servers)
})
})
describe('With manual strategy', function () {
before(function () {
this.timeout(240000)
return createServers(null)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy('manual')
})
it('Should create a redundancy on first video', async function () {
await servers[0].redundancy.addVideo({ videoId: video1Server2.id })
})
it('Should have 2 webseeds on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy('manual')
})
it('Should manually remove redundancies on server 1 and remove duplicated videos', async function () {
this.timeout(80000)
const body = await servers[0].redundancy.listVideos({ target: 'remote-videos' })
const videos = body.data
expect(videos).to.have.lengthOf(1)
const video = videos[0]
for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) {
await servers[0].redundancy.removeVideo({ redundancyId: r.id })
}
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
})
after(async function () {
await cleanupTests(servers)
})
})
describe('Test expiration', function () {
const strategy = 'recently-added'
async function checkContains (servers: PeerTubeServer[], str: string) {
for (const server of servers) {
const video = await server.videos.get({ id: video1Server2.uuid })
for (const f of video.files) {
expect(f.magnetUri).to.contain(str)
}
}
}
async function checkNotContains (servers: PeerTubeServer[], str: string) {
for (const server of servers) {
const video = await server.videos.get({ id: video1Server2.uuid })
for (const f of video.files) {
expect(f.magnetUri).to.not.contain(str)
}
}
}
before(async function () {
this.timeout(240000)
await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
await enableRedundancyOnServer1()
})
it('Should still have 2 webseeds after 10 seconds', async function () {
this.timeout(80000)
await wait(10000)
try {
await checkContains(servers, 'http%3A%2F%2F' + servers[0].hostname + '%3A' + servers[0].port)
} catch {
// Maybe a server deleted a redundancy in the scheduler
await wait(2000)
await checkContains(servers, 'http%3A%2F%2F' + servers[0].hostname + '%3A' + servers[0].port)
}
})
it('Should stop server 1 and expire video redundancy', async function () {
this.timeout(80000)
await killallServers([ servers[0] ])
await wait(15000)
await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2F' + servers[0].port + '%3A' + servers[0].port)
})
after(async function () {
await cleanupTests(servers)
})
})
describe('Test file replacement', function () {
let video2Server2UUID: string
const strategy = 'recently-added'
before(async function () {
this.timeout(240000)
await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
await enableRedundancyOnServer1()
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
const { uuid } = await servers[1].videos.upload({ attributes: { name: 'video 2 server 2', privacy: VideoPrivacy.PRIVATE } })
video2Server2UUID = uuid
// Wait transcoding before federation
await waitJobs(servers)
await servers[1].videos.update({ id: video2Server2UUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
})
it('Should cache video 2 webseeds on the first video', async function () {
this.timeout(240000)
await waitJobs(servers)
let checked = false
while (checked === false) {
await wait(1000)
try {
await check1WebSeed()
await check0PlaylistRedundancies()
await check2Webseeds(video2Server2UUID)
await check1PlaylistRedundancies(video2Server2UUID)
checked = true
} catch {
checked = false
}
}
})
it('Should disable strategy and remove redundancies', async function () {
this.timeout(80000)
await waitJobs(servers)
await killallServers([ servers[0] ])
await servers[0].run({
redundancy: {
videos: {
check_interval: '1 second',
strategies: []
}
}
})
await waitJobs(servers)
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
})
after(async function () {
await cleanupTests(servers)
})
})
})
+6
ファイルの表示
@@ -0,0 +1,6 @@
export * from './runner-common.js'
export * from './runner-live-transcoding.js'
export * from './runner-socket.js'
export * from './runner-studio-transcoding.js'
export * from './runner-transcription.js'
export * from './runner-vod-transcoding.js'
+744
ファイルの表示
@@ -0,0 +1,744 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
Runner,
RunnerJob,
RunnerJobAdmin,
RunnerJobState,
RunnerJobStateType,
RunnerJobVODWebVideoTranscodingPayload,
RunnerRegistrationToken
} from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createSingleServer,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
describe('Test runner common actions', function () {
let server: PeerTubeServer
let registrationToken: string
let runnerToken: string
let jobMaxPriority: string
before(async function () {
this.timeout(120_000)
server = await createSingleServer(1, {
remote_runners: {
stalled_jobs: {
vod: '5 seconds'
}
}
})
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableTranscoding({ hls: true, webVideo: true })
await server.config.enableRemoteTranscoding()
})
describe('Managing runner registration tokens', function () {
let base: RunnerRegistrationToken[]
let registrationTokenToDelete: RunnerRegistrationToken
it('Should have a default registration token', async function () {
const { total, data } = await server.runnerRegistrationTokens.list()
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
const token = data[0]
expect(token.id).to.exist
expect(token.createdAt).to.exist
expect(token.updatedAt).to.exist
expect(token.registeredRunnersCount).to.equal(0)
expect(token.registrationToken).to.exist
})
it('Should create other registration tokens', async function () {
await server.runnerRegistrationTokens.generate()
await server.runnerRegistrationTokens.generate()
const { total, data } = await server.runnerRegistrationTokens.list()
expect(total).to.equal(3)
expect(data).to.have.lengthOf(3)
})
it('Should list registration tokens', async function () {
{
const { total, data } = await server.runnerRegistrationTokens.list({ sort: 'createdAt' })
expect(total).to.equal(3)
expect(data).to.have.lengthOf(3)
expect(new Date(data[0].createdAt)).to.be.below(new Date(data[1].createdAt))
expect(new Date(data[1].createdAt)).to.be.below(new Date(data[2].createdAt))
base = data
registrationTokenToDelete = data[0]
registrationToken = data[1].registrationToken
}
{
const { total, data } = await server.runnerRegistrationTokens.list({ sort: '-createdAt', start: 2, count: 1 })
expect(total).to.equal(3)
expect(data).to.have.lengthOf(1)
expect(data[0].registrationToken).to.equal(base[0].registrationToken)
}
})
it('Should have appropriate registeredRunnersCount for registration tokens', async function () {
await server.runners.register({ name: 'to delete 1', registrationToken: registrationTokenToDelete.registrationToken })
await server.runners.register({ name: 'to delete 2', registrationToken: registrationTokenToDelete.registrationToken })
const { data } = await server.runnerRegistrationTokens.list()
for (const d of data) {
if (d.registrationToken === registrationTokenToDelete.registrationToken) {
expect(d.registeredRunnersCount).to.equal(2)
} else {
expect(d.registeredRunnersCount).to.equal(0)
}
}
const { data: runners } = await server.runners.list()
expect(runners).to.have.lengthOf(2)
})
it('Should delete a registration token', async function () {
await server.runnerRegistrationTokens.delete({ id: registrationTokenToDelete.id })
const { total, data } = await server.runnerRegistrationTokens.list({ sort: 'createdAt' })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
for (const d of data) {
expect(d.registeredRunnersCount).to.equal(0)
expect(d.registrationToken).to.not.equal(registrationTokenToDelete.registrationToken)
}
})
it('Should have removed runners of this registration token', async function () {
const { data: runners } = await server.runners.list()
expect(runners).to.have.lengthOf(0)
})
})
describe('Managing runners', function () {
let toDelete: Runner
it('Should not have runners available', async function () {
const { total, data } = await server.runners.list()
expect(data).to.have.lengthOf(0)
expect(total).to.equal(0)
})
it('Should register runners', async function () {
const now = new Date()
const result = await server.runners.register({
name: 'runner 1',
description: 'my super runner 1',
registrationToken
})
expect(result.runnerToken).to.exist
runnerToken = result.runnerToken
await server.runners.register({
name: 'runner 2',
registrationToken
})
const { total, data } = await server.runners.list({ sort: 'createdAt' })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
for (const d of data) {
expect(d.id).to.exist
expect(d.createdAt).to.exist
expect(d.updatedAt).to.exist
expect(new Date(d.createdAt)).to.be.above(now)
expect(new Date(d.updatedAt)).to.be.above(now)
expect(new Date(d.lastContact)).to.be.above(now)
expect(d.ip).to.exist
}
expect(data[0].name).to.equal('runner 1')
expect(data[0].description).to.equal('my super runner 1')
expect(data[1].name).to.equal('runner 2')
expect(data[1].description).to.be.null
toDelete = data[1]
})
it('Should list runners', async function () {
const { total, data } = await server.runners.list({ sort: '-createdAt', start: 1, count: 1 })
expect(total).to.equal(2)
expect(data).to.have.lengthOf(1)
expect(data[0].name).to.equal('runner 1')
})
it('Should delete a runner', async function () {
await server.runners.delete({ id: toDelete.id })
const { total, data } = await server.runners.list()
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].name).to.equal('runner 1')
})
it('Should unregister a runner', async function () {
const registered = await server.runners.autoRegisterRunner()
{
const { total, data } = await server.runners.list()
expect(total).to.equal(2)
expect(data).to.have.lengthOf(2)
}
await server.runners.unregister({ runnerToken: registered })
{
const { total, data } = await server.runners.list()
expect(total).to.equal(1)
expect(data).to.have.lengthOf(1)
expect(data[0].name).to.equal('runner 1')
}
})
})
describe('Managing runner jobs', function () {
let jobUUID: string
let jobToken: string
let lastRunnerContact: Date
let failedJob: RunnerJob
async function checkMainJobState (
mainJobState: RunnerJobStateType,
otherJobStates: RunnerJobStateType[] = [ RunnerJobState.PENDING, RunnerJobState.WAITING_FOR_PARENT_JOB ]
) {
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
for (const job of data) {
if (job.uuid === jobUUID) {
expect(job.state.id).to.equal(mainJobState)
} else {
expect(otherJobStates).to.include(job.state.id)
}
}
}
function getMainJob () {
return server.runnerJobs.getJob({ uuid: jobUUID })
}
describe('List jobs', function () {
it('Should not have jobs', async function () {
const { total, data } = await server.runnerJobs.list()
expect(data).to.have.lengthOf(0)
expect(total).to.equal(0)
})
it('Should upload a video and have available jobs', async function () {
await server.videos.quickUpload({ name: 'to transcode' })
await waitJobs([ server ])
const { total, data } = await server.runnerJobs.list()
expect(data).to.have.lengthOf(10)
expect(total).to.equal(10)
for (const job of data) {
expect(job.startedAt).to.not.exist
expect(job.finishedAt).to.not.exist
expect(job.payload).to.exist
expect(job.privatePayload).to.exist
}
const hlsJobs = data.filter(d => d.type === 'vod-hls-transcoding')
const webVideoJobs = data.filter(d => d.type === 'vod-web-video-transcoding')
expect(hlsJobs).to.have.lengthOf(5)
expect(webVideoJobs).to.have.lengthOf(5)
const pendingJobs = data.filter(d => d.state.id === RunnerJobState.PENDING)
const waitingJobs = data.filter(d => d.state.id === RunnerJobState.WAITING_FOR_PARENT_JOB)
expect(pendingJobs).to.have.lengthOf(1)
expect(waitingJobs).to.have.lengthOf(9)
})
it('Should upload another video and list/sort jobs', async function () {
await server.videos.quickUpload({ name: 'to transcode 2' })
await waitJobs([ server ])
{
const { total, data } = await server.runnerJobs.list({ start: 0, count: 30 })
expect(data).to.have.lengthOf(20)
expect(total).to.equal(20)
jobUUID = data[16].uuid
}
{
const { total, data } = await server.runnerJobs.list({ start: 3, count: 1, sort: 'createdAt' })
expect(total).to.equal(20)
expect(data).to.have.lengthOf(1)
expect(data[0].uuid).to.equal(jobUUID)
}
{
let previousPriority = Infinity
const { total, data } = await server.runnerJobs.list({ start: 0, count: 100, sort: '-priority' })
expect(total).to.equal(20)
for (const job of data) {
expect(job.priority).to.be.at.most(previousPriority)
previousPriority = job.priority
if (job.state.id === RunnerJobState.PENDING) {
jobMaxPriority = job.uuid
}
}
}
})
it('Should search jobs', async function () {
{
const { total, data } = await server.runnerJobs.list({ search: jobUUID })
expect(data).to.have.lengthOf(1)
expect(total).to.equal(1)
expect(data[0].uuid).to.equal(jobUUID)
}
{
const { total, data } = await server.runnerJobs.list({ search: 'toto' })
expect(data).to.have.lengthOf(0)
expect(total).to.equal(0)
}
{
const { total, data } = await server.runnerJobs.list({ search: 'hls' })
expect(data).to.not.have.lengthOf(0)
expect(total).to.not.equal(0)
for (const job of data) {
expect(job.type).to.include('hls')
}
}
})
it('Should filter jobs', async function () {
{
const { total, data } = await server.runnerJobs.list({ stateOneOf: [ RunnerJobState.WAITING_FOR_PARENT_JOB ] })
expect(data).to.not.have.lengthOf(0)
expect(total).to.not.equal(0)
for (const job of data) {
expect(job.state.label).to.equal('Waiting for parent job to finish')
}
}
{
const { total, data } = await server.runnerJobs.list({ stateOneOf: [ RunnerJobState.COMPLETED ] })
expect(data).to.have.lengthOf(0)
expect(total).to.equal(0)
}
})
})
describe('Accept/update/abort/process a job', function () {
it('Should request available jobs', async function () {
lastRunnerContact = new Date()
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
// Only optimize jobs are available
expect(availableJobs).to.have.lengthOf(2)
for (const job of availableJobs) {
expect(job.uuid).to.exist
expect(job.payload.input).to.exist
expect((job.payload as RunnerJobVODWebVideoTranscodingPayload).output).to.exist
expect((job as RunnerJobAdmin).privatePayload).to.not.exist
}
const hlsJobs = availableJobs.filter(d => d.type === 'vod-hls-transcoding')
const webVideoJobs = availableJobs.filter(d => d.type === 'vod-web-video-transcoding')
expect(hlsJobs).to.have.lengthOf(0)
expect(webVideoJobs).to.have.lengthOf(2)
jobUUID = webVideoJobs[0].uuid
})
it('Should have sorted available jobs by priority', async function () {
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
expect(availableJobs[0].uuid).to.equal(jobMaxPriority)
})
it('Should have last runner contact updated', async function () {
await wait(1000)
const { data } = await server.runners.list({ sort: 'createdAt' })
expect(new Date(data[0].lastContact)).to.be.above(lastRunnerContact)
})
it('Should accept a job', async function () {
const startedAt = new Date()
const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID })
jobToken = job.jobToken
const checkProcessingJob = (job: RunnerJob & { jobToken?: string }, fromAccept: boolean) => {
expect(job.uuid).to.equal(jobUUID)
expect(job.type).to.equal('vod-web-video-transcoding')
expect(job.state.label).to.equal('Processing')
expect(job.state.id).to.equal(RunnerJobState.PROCESSING)
expect(job.runner).to.exist
expect(job.runner.name).to.equal('runner 1')
expect(job.runner.description).to.equal('my super runner 1')
expect(job.progress).to.be.null
expect(job.startedAt).to.exist
expect(new Date(job.startedAt)).to.be.above(startedAt)
expect(job.finishedAt).to.not.exist
expect(job.failures).to.equal(0)
expect(job.payload).to.exist
if (fromAccept) {
expect(job.jobToken).to.exist
expect((job as RunnerJobAdmin).privatePayload).to.not.exist
} else {
expect(job.jobToken).to.not.exist
expect((job as RunnerJobAdmin).privatePayload).to.exist
}
}
checkProcessingJob(job, true)
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
const processingJob = data.find(j => j.uuid === jobUUID)
checkProcessingJob(processingJob, false)
await checkMainJobState(RunnerJobState.PROCESSING)
})
it('Should update a job', async function () {
await server.runnerJobs.update({ runnerToken, jobUUID, jobToken, progress: 53 })
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
for (const job of data) {
if (job.state.id === RunnerJobState.PROCESSING) {
expect(job.progress).to.equal(53)
} else {
expect(job.progress).to.be.null
}
}
})
it('Should abort a job', async function () {
await server.runnerJobs.abort({ runnerToken, jobUUID, jobToken, reason: 'for tests' })
await checkMainJobState(RunnerJobState.PENDING)
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
for (const job of data) {
expect(job.progress).to.be.null
}
})
it('Should accept the same job again and post a success', async function () {
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
expect(availableJobs.find(j => j.uuid === jobUUID)).to.exist
const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID })
jobToken = job.jobToken
await checkMainJobState(RunnerJobState.PROCESSING)
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
for (const job of data) {
expect(job.progress).to.be.null
}
const payload = {
videoFile: 'video_short.mp4'
}
await server.runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
})
it('Should not have available jobs anymore', async function () {
await checkMainJobState(RunnerJobState.COMPLETED)
const job = await getMainJob()
expect(job.finishedAt).to.exist
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
expect(availableJobs.find(j => j.uuid === jobUUID)).to.not.exist
})
})
describe('Error job', function () {
it('Should accept another job and post an error', async function () {
await server.runnerJobs.cancelAllJobs()
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
jobUUID = availableJobs[0].uuid
const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID })
jobToken = job.jobToken
await server.runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' })
})
it('Should have job failures increased', async function () {
const job = await getMainJob()
expect(job.state.id).to.equal(RunnerJobState.PENDING)
expect(job.failures).to.equal(1)
expect(job.error).to.be.null
expect(job.progress).to.be.null
expect(job.finishedAt).to.not.exist
})
it('Should error a job when job attempts is too big', async function () {
for (let i = 0; i < 4; i++) {
const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID })
jobToken = job.jobToken
await server.runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error ' + i })
}
const job = await getMainJob()
expect(job.failures).to.equal(5)
expect(job.state.id).to.equal(RunnerJobState.ERRORED)
expect(job.state.label).to.equal('Errored')
expect(job.error).to.equal('Error 3')
expect(job.progress).to.be.null
expect(job.finishedAt).to.exist
failedJob = job
})
it('Should have failed children jobs too', async function () {
const { data } = await server.runnerJobs.list({ count: 50, sort: '-updatedAt' })
const children = data.filter(j => j.parent?.uuid === failedJob.uuid)
expect(children).to.have.lengthOf(9)
for (const child of children) {
expect(child.parent.uuid).to.equal(failedJob.uuid)
expect(child.parent.type).to.equal(failedJob.type)
expect(child.parent.state.id).to.equal(failedJob.state.id)
expect(child.parent.state.label).to.equal(failedJob.state.label)
expect(child.state.id).to.equal(RunnerJobState.PARENT_ERRORED)
expect(child.state.label).to.equal('Parent job failed')
}
})
})
describe('Cancel', function () {
it('Should cancel a pending job', async function () {
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
{
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
const pendingJob = data.find(j => j.state.id === RunnerJobState.PENDING)
jobUUID = pendingJob.uuid
await server.runnerJobs.cancelByAdmin({ jobUUID })
}
{
const job = await getMainJob()
expect(job.state.id).to.equal(RunnerJobState.CANCELLED)
expect(job.state.label).to.equal('Cancelled')
}
{
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
const children = data.filter(j => j.parent?.uuid === jobUUID)
expect(children).to.have.lengthOf(9)
for (const child of children) {
expect(child.state.id).to.equal(RunnerJobState.PARENT_CANCELLED)
}
}
})
it('Should cancel an already accepted job and skip success/error', async function () {
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
jobUUID = availableJobs[0].uuid
const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID })
jobToken = job.jobToken
await server.runnerJobs.cancelByAdmin({ jobUUID })
await server.runnerJobs.abort({ runnerToken, jobUUID, jobToken, reason: 'aborted', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
})
describe('Remove', function () {
it('Should remove a pending job', async function () {
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
{
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
const pendingJob = data.find(j => j.state.id === RunnerJobState.PENDING)
jobUUID = pendingJob.uuid
await server.runnerJobs.deleteByAdmin({ jobUUID })
}
{
const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' })
const parent = data.find(j => j.uuid === jobUUID)
expect(parent).to.not.exist
const children = data.filter(j => j.parent?.uuid === jobUUID)
expect(children).to.have.lengthOf(0)
}
})
})
describe('Stalled jobs', function () {
it('Should abort stalled jobs', async function () {
this.timeout(60000)
await server.videos.quickUpload({ name: 'video' })
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { job: job1 } = await server.runnerJobs.autoAccept({ runnerToken })
const { job: stalledJob } = await server.runnerJobs.autoAccept({ runnerToken })
for (let i = 0; i < 6; i++) {
await wait(2000)
await server.runnerJobs.update({ runnerToken, jobToken: job1.jobToken, jobUUID: job1.uuid })
}
const refreshedJob1 = await server.runnerJobs.getJob({ uuid: job1.uuid })
const refreshedStalledJob = await server.runnerJobs.getJob({ uuid: stalledJob.uuid })
expect(refreshedJob1.state.id).to.equal(RunnerJobState.PROCESSING)
expect(refreshedStalledJob.state.id).to.equal(RunnerJobState.PENDING)
})
})
describe('Rate limit', function () {
before(async function () {
this.timeout(60000)
await server.kill()
await server.run({
rates_limit: {
api: {
max: 10
}
}
})
})
it('Should rate limit an unknown runner, but not a registered one', async function () {
this.timeout(60000)
await server.videos.quickUpload({ name: 'video' })
await waitJobs([ server ])
const { job } = await server.runnerJobs.autoAccept({ runnerToken })
for (let i = 0; i < 20; i++) {
try {
await server.runnerJobs.request({ runnerToken })
await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid })
} catch {}
}
// Invalid
{
await server.runnerJobs.request({ runnerToken: 'toto', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
await server.runnerJobs.update({
runnerToken: 'toto',
jobToken: job.jobToken,
jobUUID: job.uuid,
expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429
})
}
// Not provided
{
await server.runnerJobs.request({ runnerToken: undefined, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
await server.runnerJobs.update({
runnerToken: undefined,
jobToken: job.jobToken,
jobUUID: job.uuid,
expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429
})
}
// Registered
{
await server.runnerJobs.request({ runnerToken })
await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid })
}
})
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+332
ファイルの表示
@@ -0,0 +1,332 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { FfmpegCommand } from 'fluent-ffmpeg'
import { readFile } from 'fs/promises'
import { wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
LiveRTMPHLSTranscodingUpdatePayload,
LiveVideo,
LiveVideoError,
LiveVideoErrorType,
RunnerJob,
RunnerJobLiveRTMPHLSTranscodingPayload,
Video,
VideoPrivacy,
VideoState
} from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
cleanupTests,
createSingleServer,
makeRawRequest,
PeerTubeServer,
sendRTMPStream,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
testFfmpegStreamError,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test runner live transcoding', function () {
let server: PeerTubeServer
let runnerToken: string
let baseUrl: string
before(async function () {
this.timeout(120_000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableRemoteTranscoding()
await server.config.enableTranscoding()
runnerToken = await server.runners.autoRegisterRunner()
baseUrl = server.url + '/static/streaming-playlists/hls'
})
describe('Without transcoding enabled', function () {
before(async function () {
await server.config.enableLive({
allowReplay: false,
resolutions: 'min',
transcoding: false
})
})
it('Should not have available jobs', async function () {
this.timeout(120000)
const { live, video } = await server.live.quickCreate({ permanentLive: true, saveReplay: false, privacy: VideoPrivacy.PUBLIC })
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await server.live.waitUntilPublished({ videoId: video.id })
await waitJobs([ server ])
const { availableJobs } = await server.runnerJobs.requestLive({ runnerToken })
expect(availableJobs).to.have.lengthOf(0)
await stopFfmpeg(ffmpegCommand)
})
})
describe('With transcoding enabled on classic live', function () {
let live: LiveVideo
let video: Video
let ffmpegCommand: FfmpegCommand
let jobUUID: string
let acceptedJob: RunnerJob & { jobToken: string }
async function testPlaylistFile (fixture: string, expected: string) {
const text = await server.streamingPlaylists.get({ url: `${baseUrl}/${video.uuid}/${fixture}` })
expect(await readFile(buildAbsoluteFixturePath(expected), 'utf-8')).to.equal(text)
}
async function testTSFile (fixture: string, expected: string) {
const { body } = await makeRawRequest({ url: `${baseUrl}/${video.uuid}/${fixture}`, expectedStatus: HttpStatusCode.OK_200 })
expect(await readFile(buildAbsoluteFixturePath(expected))).to.deep.equal(body)
}
before(async function () {
await server.config.enableLive({
allowReplay: true,
resolutions: 'max',
transcoding: true
})
})
it('Should publish a a live and have available jobs', async function () {
this.timeout(120000)
const data = await server.live.quickCreate({ permanentLive: false, saveReplay: false, privacy: VideoPrivacy.PUBLIC })
live = data.live
video = data.video
ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await waitJobs([ server ])
const job = await server.runnerJobs.requestLiveJob(runnerToken)
jobUUID = job.uuid
expect(job.type).to.equal('live-rtmp-hls-transcoding')
expect(job.payload.input.rtmpUrl).to.exist
expect(job.payload.output.toTranscode).to.have.lengthOf(5)
for (const { resolution, fps } of job.payload.output.toTranscode) {
expect([ 720, 480, 360, 240, 144 ]).to.contain(resolution)
expect(fps).to.be.above(25)
expect(fps).to.be.below(70)
}
})
it('Should update the live with a new chunk', async function () {
this.timeout(120000)
const { job } = await server.runnerJobs.accept<RunnerJobLiveRTMPHLSTranscodingPayload>({ jobUUID, runnerToken })
acceptedJob = job
{
const payload: LiveRTMPHLSTranscodingUpdatePayload = {
masterPlaylistFile: 'live/master.m3u8',
resolutionPlaylistFile: 'live/0.m3u8',
resolutionPlaylistFilename: '0.m3u8',
type: 'add-chunk',
videoChunkFile: 'live/0-000067.ts',
videoChunkFilename: '0-000067.ts'
}
await server.runnerJobs.update({ jobUUID, runnerToken, jobToken: job.jobToken, payload, progress: 50 })
const updatedJob = await server.runnerJobs.getJob({ uuid: job.uuid })
expect(updatedJob.progress).to.equal(50)
}
{
const payload: LiveRTMPHLSTranscodingUpdatePayload = {
resolutionPlaylistFile: 'live/1.m3u8',
resolutionPlaylistFilename: '1.m3u8',
type: 'add-chunk',
videoChunkFile: 'live/1-000068.ts',
videoChunkFilename: '1-000068.ts'
}
await server.runnerJobs.update({ jobUUID, runnerToken, jobToken: job.jobToken, payload })
}
await wait(1000)
await testPlaylistFile('master.m3u8', 'live/master.m3u8')
await testPlaylistFile('0.m3u8', 'live/0.m3u8')
await testPlaylistFile('1.m3u8', 'live/1.m3u8')
await testTSFile('0-000067.ts', 'live/0-000067.ts')
await testTSFile('1-000068.ts', 'live/1-000068.ts')
})
it('Should replace existing m3u8 on update', async function () {
this.timeout(120000)
const payload: LiveRTMPHLSTranscodingUpdatePayload = {
masterPlaylistFile: 'live/1.m3u8',
resolutionPlaylistFilename: '0.m3u8',
resolutionPlaylistFile: 'live/1.m3u8',
type: 'add-chunk',
videoChunkFile: 'live/1-000069.ts',
videoChunkFilename: '1-000068.ts'
}
await server.runnerJobs.update({ jobUUID, runnerToken, jobToken: acceptedJob.jobToken, payload })
await wait(1000)
await testPlaylistFile('master.m3u8', 'live/1.m3u8')
await testPlaylistFile('0.m3u8', 'live/1.m3u8')
await testTSFile('1-000068.ts', 'live/1-000069.ts')
})
it('Should update the live with removed chunks', async function () {
this.timeout(120000)
const payload: LiveRTMPHLSTranscodingUpdatePayload = {
resolutionPlaylistFile: 'live/0.m3u8',
resolutionPlaylistFilename: '0.m3u8',
type: 'remove-chunk',
videoChunkFilename: '1-000068.ts'
}
await server.runnerJobs.update({ jobUUID, runnerToken, jobToken: acceptedJob.jobToken, payload })
await wait(1000)
await server.streamingPlaylists.get({ url: `${baseUrl}/${video.uuid}/master.m3u8` })
await server.streamingPlaylists.get({ url: `${baseUrl}/${video.uuid}/0.m3u8` })
await server.streamingPlaylists.get({ url: `${baseUrl}/${video.uuid}/1.m3u8` })
await makeRawRequest({ url: `${baseUrl}/${video.uuid}/0-000067.ts`, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: `${baseUrl}/${video.uuid}/1-000068.ts`, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
})
it('Should complete the live and save the replay', async function () {
this.timeout(120000)
for (const segment of [ '0-000069.ts', '0-000070.ts' ]) {
const payload: LiveRTMPHLSTranscodingUpdatePayload = {
masterPlaylistFile: 'live/master.m3u8',
resolutionPlaylistFilename: '0.m3u8',
resolutionPlaylistFile: 'live/0.m3u8',
type: 'add-chunk',
videoChunkFile: 'live/' + segment,
videoChunkFilename: segment
}
await server.runnerJobs.update({ jobUUID, runnerToken, jobToken: acceptedJob.jobToken, payload })
await wait(1000)
}
await waitJobs([ server ])
{
const { state } = await server.videos.get({ id: video.uuid })
expect(state.id).to.equal(VideoState.PUBLISHED)
}
await stopFfmpeg(ffmpegCommand)
await server.runnerJobs.success({ jobUUID, runnerToken, jobToken: acceptedJob.jobToken, payload: {} })
await wait(1500)
await waitJobs([ server ])
{
const { state } = await server.videos.get({ id: video.uuid })
expect(state.id).to.equal(VideoState.LIVE_ENDED)
const session = await server.live.findLatestSession({ videoId: video.uuid })
expect(session.error).to.be.null
}
})
})
describe('With transcoding enabled on cancelled/aborted/errored live', function () {
let live: LiveVideo
let video: Video
let ffmpegCommand: FfmpegCommand
async function prepare () {
ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
await server.runnerJobs.requestLiveJob(runnerToken)
const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'live-rtmp-hls-transcoding' })
return job
}
async function checkSessionError (error: LiveVideoErrorType) {
await wait(1500)
await waitJobs([ server ])
const session = await server.live.findLatestSession({ videoId: video.uuid })
expect(session.error).to.equal(error)
}
before(async function () {
await server.config.enableLive({
allowReplay: true,
resolutions: 'max',
transcoding: true
})
const data = await server.live.quickCreate({ permanentLive: true, saveReplay: false, privacy: VideoPrivacy.PUBLIC })
live = data.live
video = data.video
})
it('Should abort a running live', async function () {
this.timeout(120000)
const job = await prepare()
await Promise.all([
server.runnerJobs.abort({ jobUUID: job.uuid, runnerToken, jobToken: job.jobToken, reason: 'abort' }),
testFfmpegStreamError(ffmpegCommand, true)
])
// Abort is not supported
await checkSessionError(LiveVideoError.RUNNER_JOB_ERROR)
})
it('Should cancel a running live', async function () {
this.timeout(120000)
const job = await prepare()
await Promise.all([
server.runnerJobs.cancelByAdmin({ jobUUID: job.uuid }),
testFfmpegStreamError(ffmpegCommand, true)
])
await checkSessionError(LiveVideoError.RUNNER_JOB_CANCEL)
})
it('Should error a running live', async function () {
this.timeout(120000)
const job = await prepare()
await Promise.all([
server.runnerJobs.error({ jobUUID: job.uuid, runnerToken, jobToken: job.jobToken, message: 'error' }),
testFfmpegStreamError(ffmpegCommand, true)
])
await checkSessionError(LiveVideoError.RUNNER_JOB_ERROR)
})
})
after(async function () {
await cleanupTests([ server ])
})
})
+120
ファイルの表示
@@ -0,0 +1,120 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { wait } from '@peertube/peertube-core-utils'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test runner socket', function () {
let server: PeerTubeServer
let runnerToken: string
before(async function () {
this.timeout(120_000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableTranscoding({ hls: true, webVideo: true })
await server.config.enableRemoteTranscoding()
runnerToken = await server.runners.autoRegisterRunner()
})
it('Should throw an error without runner token', function (done) {
const localSocket = server.socketIO.getRunnersSocket({ runnerToken: null })
localSocket.on('connect_error', err => {
expect(err.message).to.contain('No runner token provided')
done()
})
})
it('Should throw an error with a bad runner token', function (done) {
const localSocket = server.socketIO.getRunnersSocket({ runnerToken: 'ergag' })
localSocket.on('connect_error', err => {
expect(err.message).to.contain('Invalid runner token')
done()
})
})
it('Should not send ping if there is no available jobs', async function () {
let pings = 0
const localSocket = server.socketIO.getRunnersSocket({ runnerToken })
localSocket.on('available-jobs', () => pings++)
expect(pings).to.equal(0)
})
it('Should send a ping on available job', async function () {
let pings = 0
const localSocket = server.socketIO.getRunnersSocket({ runnerToken })
localSocket.on('available-jobs', () => pings++)
await server.videos.quickUpload({ name: 'video1' })
await waitJobs([ server ])
// eslint-disable-next-line no-unmodified-loop-condition
while (pings !== 1) {
await wait(500)
}
await server.videos.quickUpload({ name: 'video2' })
await waitJobs([ server ])
// eslint-disable-next-line no-unmodified-loop-condition
while ((pings as number) !== 2) {
await wait(500)
}
await server.runnerJobs.cancelAllJobs()
})
it('Should send a ping when a child is ready', async function () {
let pings = 0
const localSocket = server.socketIO.getRunnersSocket({ runnerToken })
localSocket.on('available-jobs', () => pings++)
await server.videos.quickUpload({ name: 'video3' })
await waitJobs([ server ])
// eslint-disable-next-line no-unmodified-loop-condition
while (pings !== 1) {
await wait(500)
}
await server.runnerJobs.autoProcessWebVideoJob(runnerToken)
await waitJobs([ server ])
// eslint-disable-next-line no-unmodified-loop-condition
while ((pings as number) !== 2) {
await wait(500)
}
})
it('Should not send a ping if the ended job does not have a child', async function () {
let pings = 0
const localSocket = server.socketIO.getRunnersSocket({ runnerToken })
localSocket.on('available-jobs', () => pings++)
const { availableJobs } = await server.runnerJobs.request({ runnerToken })
const job = availableJobs.find(j => j.type === 'vod-web-video-transcoding')
await server.runnerJobs.autoProcessWebVideoJob(runnerToken, job.uuid)
// Wait for debounce
await wait(1000)
await waitJobs([ server ])
expect(pings).to.equal(0)
})
after(async function () {
await cleanupTests([ server ])
})
})
+169
ファイルの表示
@@ -0,0 +1,169 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { readFile } from 'fs/promises'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
RunnerJobStudioTranscodingPayload,
VideoStudioTranscodingSuccess,
VideoState,
VideoStudioTask,
VideoStudioTaskIntro
} from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
VideoStudioCommand,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkVideoDuration } from '@tests/shared/checks.js'
import { checkPersistentTmpIsEmpty } from '@tests/shared/directories.js'
describe('Test runner video studio transcoding', function () {
let servers: PeerTubeServer[] = []
let runnerToken: string
let videoUUID: string
let jobUUID: string
async function renewStudio (tasks: VideoStudioTask[] = VideoStudioCommand.getComplexTask()) {
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
videoUUID = uuid
await waitJobs(servers)
await servers[0].videoStudio.createEditionTasks({ videoId: uuid, tasks })
await waitJobs(servers)
const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
expect(availableJobs).to.have.lengthOf(1)
jobUUID = availableJobs[0].uuid
}
before(async function () {
this.timeout(120_000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
await servers[0].config.enableStudio()
await servers[0].config.enableRemoteStudio()
runnerToken = await servers[0].runners.autoRegisterRunner()
})
it('Should error a studio transcoding job', async function () {
this.timeout(60000)
await renewStudio()
for (let i = 0; i < 5; i++) {
const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID })
const jobToken = job.jobToken
await servers[0].runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' })
}
const video = await servers[0].videos.get({ id: videoUUID })
expect(video.state.id).to.equal(VideoState.PUBLISHED)
await checkPersistentTmpIsEmpty(servers[0])
})
it('Should cancel a transcoding job', async function () {
this.timeout(60000)
await renewStudio()
await servers[0].runnerJobs.cancelByAdmin({ jobUUID })
const video = await servers[0].videos.get({ id: videoUUID })
expect(video.state.id).to.equal(VideoState.PUBLISHED)
await checkPersistentTmpIsEmpty(servers[0])
})
it('Should execute a remote studio job', async function () {
this.timeout(240_000)
const tasks = [
{
name: 'add-outro' as 'add-outro',
options: {
file: 'video_short.webm'
}
},
{
name: 'add-watermark' as 'add-watermark',
options: {
file: 'custom-thumbnail.png'
}
},
{
name: 'add-intro' as 'add-intro',
options: {
file: 'video_very_short_240p.mp4'
}
}
]
await renewStudio(tasks)
for (const server of servers) {
await checkVideoDuration(server, videoUUID, 5)
}
const { job } = await servers[0].runnerJobs.accept<RunnerJobStudioTranscodingPayload>({ runnerToken, jobUUID })
const jobToken = job.jobToken
expect(job.type === 'video-studio-transcoding')
expect(job.payload.input.videoFileUrl).to.exist
// Check video input file
{
await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
}
// Check task files
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i]
const payloadTask = job.payload.tasks[i]
expect(payloadTask.name).to.equal(task.name)
const inputFile = await readFile(buildAbsoluteFixturePath(task.options.file))
const { body } = await servers[0].runnerJobs.getJobFile({
url: (payloadTask as VideoStudioTaskIntro).options.file as string,
jobToken,
runnerToken
})
expect(body).to.deep.equal(inputFile)
}
const payload: VideoStudioTranscodingSuccess = { videoFile: 'video_very_short_240p.mp4' }
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
await waitJobs(servers)
for (const server of servers) {
await checkVideoDuration(server, videoUUID, 2)
}
await checkPersistentTmpIsEmpty(servers[0])
})
after(async function () {
await cleanupTests(servers)
})
})
+127
ファイルの表示
@@ -0,0 +1,127 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import {
RunnerJobTranscriptionPayload,
TranscriptionSuccess
} from '@peertube/peertube-models'
import {
PeerTubeServer,
cleanupTests,
createMultipleServers,
doubleFollow,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkPersistentTmpIsEmpty } from '@tests/shared/directories.js'
import { expect } from 'chai'
describe('Test runner transcription', function () {
let servers: PeerTubeServer[] = []
let runnerToken: string
before(async function () {
this.timeout(120_000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableTranscription({ remote: true })
runnerToken = await servers[0].runners.autoRegisterRunner()
})
async function upload () {
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video', language: undefined } })
await waitJobs(servers)
const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
expect(availableJobs).to.have.lengthOf(1)
const jobUUID = availableJobs[0].uuid
const { job } = await servers[0].runnerJobs.accept<RunnerJobTranscriptionPayload>({ runnerToken, jobUUID })
return { uuid, job }
}
it('Should execute a remote transcription job', async function () {
this.timeout(240_000)
const { uuid, job } = await upload()
expect(job.type === 'video-transcription')
expect(job.payload.input.videoFileUrl).to.exist
// Check video input file
{
await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken: job.jobToken, runnerToken })
}
const payload: TranscriptionSuccess = {
inputLanguage: 'ar',
vttFile: 'subtitle-good1.vtt'
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, payload })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: uuid })
expect(video.language.id).to.equal('ar')
const captions = await server.captions.list({ videoId: uuid })
expect(captions)
}
await checkPersistentTmpIsEmpty(servers[0])
})
it('Should not assign caption/language with an unknown inputLanguage', async function () {
this.timeout(240_000)
const { uuid, job } = await upload()
const payload: TranscriptionSuccess = {
inputLanguage: 'toto',
vttFile: 'subtitle-good1.vtt'
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, payload })
await waitJobs(servers)
for (const server of servers) {
const video = await server.videos.get({ id: uuid })
expect(video.language.id).to.be.null
const { total, data } = await server.captions.list({ videoId: uuid })
expect(total).to.equal(0)
expect(data).to.have.lengthOf(0)
}
})
it('Should error a transcription job and decrease the job count', async function () {
this.timeout(60000)
const { job, uuid } = await upload()
await servers[0].runnerJobs.error({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, message: 'Error' })
for (let i = 0; i < 4; i++) {
const { job: { jobToken } } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID: job.uuid })
await servers[0].runnerJobs.error({ runnerToken, jobUUID: job.uuid, jobToken, message: 'Error' })
}
await waitJobs(servers)
await servers[0].captions.runGenerate({ videoId: uuid })
})
after(async function () {
await cleanupTests(servers)
})
})
+545
ファイルの表示
@@ -0,0 +1,545 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { readFile } from 'fs/promises'
import { completeCheckHlsPlaylist } from '@tests/shared/streaming-playlists.js'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import {
HttpStatusCode,
RunnerJobSuccessPayload,
RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload,
RunnerJobVODPayload,
RunnerJobVODWebVideoTranscodingPayload,
VideoState,
VODAudioMergeTranscodingSuccess,
VODHLSTranscodingSuccess,
VODWebVideoTranscodingSuccess
} from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
doubleFollow,
makeGetRequest,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
waitJobs
} from '@peertube/peertube-server-commands'
async function processAllJobs (server: PeerTubeServer, runnerToken: string) {
do {
const { availableJobs } = await server.runnerJobs.requestVOD({ runnerToken })
if (availableJobs.length === 0) break
const { job } = await server.runnerJobs.accept<RunnerJobVODPayload>({ runnerToken, jobUUID: availableJobs[0].uuid })
const payload: RunnerJobSuccessPayload = {
videoFile: `video_short_${job.payload.output.resolution}p.mp4`,
resolutionPlaylistFile: `video_short_${job.payload.output.resolution}p.m3u8`
}
await server.runnerJobs.success({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, payload })
} while (true)
await waitJobs([ server ])
}
describe('Test runner VOD transcoding', function () {
let servers: PeerTubeServer[] = []
let runnerToken: string
before(async function () {
this.timeout(120_000)
servers = await createMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableRemoteTranscoding()
runnerToken = await servers[0].runners.autoRegisterRunner()
})
describe('Without transcoding', function () {
before(async function () {
this.timeout(60000)
await servers[0].config.disableTranscoding()
await servers[0].videos.quickUpload({ name: 'video' })
await waitJobs(servers)
})
it('Should not have available jobs', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(0)
})
})
describe('With classic transcoding enabled', function () {
before(async function () {
this.timeout(60000)
await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
})
it('Should error a transcoding job', async function () {
this.timeout(60000)
await servers[0].runnerJobs.cancelAllJobs()
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
await waitJobs(servers)
const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
const jobUUID = availableJobs[0].uuid
for (let i = 0; i < 5; i++) {
const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID })
const jobToken = job.jobToken
await servers[0].runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' })
}
const video = await servers[0].videos.get({ id: uuid })
expect(video.state.id).to.equal(VideoState.TRANSCODING_FAILED)
})
it('Should cancel a transcoding job', async function () {
await servers[0].runnerJobs.cancelAllJobs()
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
await waitJobs(servers)
const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
const jobUUID = availableJobs[0].uuid
await servers[0].runnerJobs.cancelByAdmin({ jobUUID })
const video = await servers[0].videos.get({ id: uuid })
expect(video.state.id).to.equal(VideoState.PUBLISHED)
})
})
describe('Web video transcoding only', function () {
let videoUUID: string
let jobToken: string
let jobUUID: string
before(async function () {
this.timeout(60000)
await servers[0].runnerJobs.cancelAllJobs()
await servers[0].config.enableTranscoding({ hls: false, webVideo: true })
const { uuid } = await servers[0].videos.quickUpload({ name: 'web video', fixture: 'video_short.webm' })
videoUUID = uuid
await waitJobs(servers)
})
it('Should have jobs available for remote runners', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(1)
jobUUID = availableJobs[0].uuid
})
it('Should have a valid first transcoding job', async function () {
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
expect(job.type === 'vod-web-video-transcoding')
expect(job.payload.input.videoFileUrl).to.exist
expect(job.payload.output.resolution).to.equal(720)
expect(job.payload.output.fps).to.equal(25)
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('video_short.webm'))
expect(body).to.deep.equal(inputFile)
})
it('Should transcode the max video resolution and send it back to the server', async function () {
this.timeout(60000)
const payload: VODWebVideoTranscodingSuccess = {
videoFile: 'video_short.mp4'
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(1)
expect(video.streamingPlaylists).to.have.lengthOf(0)
const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
}
})
it('Should have 4 lower resolution to transcode', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(4)
for (const resolution of [ 480, 360, 240, 144 ]) {
const job = availableJobs.find(j => j.payload.output.resolution === resolution)
expect(job).to.exist
expect(job.type).to.equal('vod-web-video-transcoding')
if (resolution === 240) jobUUID = job.uuid
}
})
it('Should process one of these transcoding jobs', async function () {
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
expect(body).to.deep.equal(inputFile)
const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${job.payload.output.resolution}p.mp4` }
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
})
it('Should process all other jobs', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(3)
for (const resolution of [ 480, 360, 144 ]) {
const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
expect(availableJob).to.exist
jobUUID = availableJob.uuid
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
expect(body).to.deep.equal(inputFile)
const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${resolution}p.mp4` }
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
}
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(5)
expect(video.streamingPlaylists).to.have.lengthOf(0)
const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
for (const file of video.files) {
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
}
}
})
it('Should not have available jobs anymore', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(0)
})
})
describe('HLS transcoding only', function () {
let videoUUID: string
let jobToken: string
let jobUUID: string
before(async function () {
this.timeout(60000)
await servers[0].config.enableTranscoding({ hls: true, webVideo: false })
const { uuid } = await servers[0].videos.quickUpload({ name: 'hls video', fixture: 'video_short.webm' })
videoUUID = uuid
await waitJobs(servers)
})
it('Should run the optimize job', async function () {
this.timeout(60000)
await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
})
it('Should have 5 HLS resolution to transcode', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(5)
for (const resolution of [ 720, 480, 360, 240, 144 ]) {
const job = availableJobs.find(j => j.payload.output.resolution === resolution)
expect(job).to.exist
expect(job.type).to.equal('vod-hls-transcoding')
if (resolution === 480) jobUUID = job.uuid
}
})
it('Should process one of these transcoding jobs', async function () {
this.timeout(60000)
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
expect(body).to.deep.equal(inputFile)
const payload: VODHLSTranscodingSuccess = {
videoFile: 'video_short_480p.mp4',
resolutionPlaylistFile: 'video_short_480p.m3u8'
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(1)
expect(video.streamingPlaylists).to.have.lengthOf(1)
const hls = video.streamingPlaylists[0]
expect(hls.files).to.have.lengthOf(1)
await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
}
})
it('Should process all other jobs', async function () {
this.timeout(60000)
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(4)
let maxQualityFile = 'video_short.mp4'
for (const resolution of [ 720, 360, 240, 144 ]) {
const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
expect(availableJob).to.exist
jobUUID = availableJob.uuid
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath(maxQualityFile))
expect(body).to.deep.equal(inputFile)
const payload: VODHLSTranscodingSuccess = {
videoFile: `video_short_${resolution}p.mp4`,
resolutionPlaylistFile: `video_short_${resolution}p.m3u8`
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
if (resolution === 720) {
maxQualityFile = 'video_short_720p.mp4'
}
}
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(0)
expect(video.streamingPlaylists).to.have.lengthOf(1)
const hls = video.streamingPlaylists[0]
expect(hls.files).to.have.lengthOf(5)
await completeCheckHlsPlaylist({ videoUUID, hlsOnly: true, servers, resolutions: [ 720, 480, 360, 240, 144 ] })
}
})
it('Should not have available jobs anymore', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(0)
})
})
describe('Web video and HLS transcoding', function () {
before(async function () {
this.timeout(60000)
await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
await servers[0].videos.quickUpload({ name: 'web video and hls video', fixture: 'video_short.webm' })
await waitJobs(servers)
})
it('Should process the first optimize job', async function () {
this.timeout(60000)
await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
})
it('Should have 9 jobs to process', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(9)
const webVideoJobs = availableJobs.filter(j => j.type === 'vod-web-video-transcoding')
const hlsJobs = availableJobs.filter(j => j.type === 'vod-hls-transcoding')
expect(webVideoJobs).to.have.lengthOf(4)
expect(hlsJobs).to.have.lengthOf(5)
})
it('Should process all available jobs', async function () {
await processAllJobs(servers[0], runnerToken)
})
})
describe('Audio merge transcoding', function () {
let videoUUID: string
let jobToken: string
let jobUUID: string
before(async function () {
this.timeout(60000)
await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' })
videoUUID = uuid
await waitJobs(servers)
})
it('Should have an audio merge transcoding job', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(1)
expect(availableJobs[0].type).to.equal('vod-audio-merge-transcoding')
jobUUID = availableJobs[0].uuid
})
it('Should have a valid remote audio merge transcoding job', async function () {
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODAudioMergeTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
expect(job.type === 'vod-audio-merge-transcoding')
expect(job.payload.input.audioFileUrl).to.exist
expect(job.payload.input.previewFileUrl).to.exist
expect(job.payload.output.resolution).to.equal(480)
{
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.audioFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('sample.ogg'))
expect(body).to.deep.equal(inputFile)
}
{
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.previewFileUrl, jobToken, runnerToken })
const video = await servers[0].videos.get({ id: videoUUID })
const { body: inputFile } = await makeGetRequest({
url: servers[0].url,
path: video.previewPath,
expectedStatus: HttpStatusCode.OK_200
})
expect(body).to.deep.equal(inputFile)
}
})
it('Should merge the audio', async function () {
this.timeout(60000)
const payload: VODAudioMergeTranscodingSuccess = { videoFile: 'video_short_480p.mp4' }
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(1)
expect(video.streamingPlaylists).to.have.lengthOf(0)
const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short_480p.mp4')))
}
})
it('Should have 7 lower resolutions to transcode', async function () {
const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
expect(availableJobs).to.have.lengthOf(7)
for (const resolution of [ 360, 240, 144 ]) {
const jobs = availableJobs.filter(j => j.payload.output.resolution === resolution)
expect(jobs).to.have.lengthOf(2)
}
jobUUID = availableJobs.find(j => j.payload.output.resolution === 480).uuid
})
it('Should process one other job', async function () {
this.timeout(60000)
const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
jobToken = job.jobToken
const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
const inputFile = await readFile(buildAbsoluteFixturePath('video_short_480p.mp4'))
expect(body).to.deep.equal(inputFile)
const payload: VODHLSTranscodingSuccess = {
videoFile: `video_short_480p.mp4`,
resolutionPlaylistFile: `video_short_480p.m3u8`
}
await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
await waitJobs(servers)
})
it('Should have the video updated', async function () {
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
expect(video.files).to.have.lengthOf(1)
expect(video.streamingPlaylists).to.have.lengthOf(1)
const hls = video.streamingPlaylists[0]
expect(hls.files).to.have.lengthOf(1)
await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
}
})
it('Should process all available jobs', async function () {
await processAllJobs(servers[0], runnerToken)
})
})
after(async function () {
await cleanupTests(servers)
})
})

変更されたファイルが多すぎるため,一部のファイルは表示されません さらに表示