Add user import/export tests
This commit is contained in:
parent
8573e5a80a
commit
f6af3f701c
51 changed files with 2852 additions and 433 deletions
|
@ -3,7 +3,7 @@
|
|||
import { decode } from 'querystring'
|
||||
import request from 'supertest'
|
||||
import { URL } from 'url'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
import { pick, queryParamsToObject } from '@peertube/peertube-core-utils'
|
||||
import { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models'
|
||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||
|
||||
|
@ -23,23 +23,33 @@ export type CommonRequestParams = {
|
|||
expectedStatus?: HttpStatusCodeType
|
||||
}
|
||||
|
||||
function makeRawRequest (options: {
|
||||
export function makeRawRequest (options: {
|
||||
url: string
|
||||
token?: string
|
||||
expectedStatus?: HttpStatusCodeType
|
||||
responseType?: string
|
||||
range?: string
|
||||
query?: { [ id: string ]: string }
|
||||
method?: 'GET' | 'POST'
|
||||
accept?: string
|
||||
headers?: { [ name: string ]: string }
|
||||
redirects?: number
|
||||
}) {
|
||||
const { host, protocol, pathname } = new URL(options.url)
|
||||
const { host, protocol, pathname, searchParams } = new URL(options.url)
|
||||
|
||||
const reqOptions = {
|
||||
url: `${protocol}//${host}`,
|
||||
path: pathname,
|
||||
|
||||
contentType: undefined,
|
||||
|
||||
...pick(options, [ 'expectedStatus', 'range', 'token', 'query', 'headers' ])
|
||||
query: {
|
||||
...(options.query || {}),
|
||||
|
||||
...queryParamsToObject(searchParams)
|
||||
},
|
||||
|
||||
...pick(options, [ 'expectedStatus', 'range', 'token', 'headers', 'responseType', 'accept', 'redirects' ])
|
||||
}
|
||||
|
||||
if (options.method === 'POST') {
|
||||
|
@ -49,7 +59,7 @@ function makeRawRequest (options: {
|
|||
return makeGetRequest(reqOptions)
|
||||
}
|
||||
|
||||
function makeGetRequest (options: CommonRequestParams & {
|
||||
export function makeGetRequest (options: CommonRequestParams & {
|
||||
query?: any
|
||||
rawQuery?: string
|
||||
}) {
|
||||
|
@ -61,7 +71,7 @@ function makeGetRequest (options: CommonRequestParams & {
|
|||
return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||
}
|
||||
|
||||
function makeHTMLRequest (url: string, path: string) {
|
||||
export function makeHTMLRequest (url: string, path: string) {
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
|
@ -70,7 +80,9 @@ function makeHTMLRequest (url: string, path: string) {
|
|||
})
|
||||
}
|
||||
|
||||
function makeActivityPubGetRequest (url: string, path: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function makeActivityPubGetRequest (url: string, path: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
|
@ -79,7 +91,17 @@ function makeActivityPubGetRequest (url: string, path: string, expectedStatus: H
|
|||
})
|
||||
}
|
||||
|
||||
function makeDeleteRequest (options: CommonRequestParams & {
|
||||
export function makeActivityPubRawRequest (url: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
||||
return makeRawRequest({
|
||||
url,
|
||||
expectedStatus,
|
||||
accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8'
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function makeDeleteRequest (options: CommonRequestParams & {
|
||||
query?: any
|
||||
rawQuery?: string
|
||||
}) {
|
||||
|
@ -91,7 +113,7 @@ function makeDeleteRequest (options: CommonRequestParams & {
|
|||
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||
}
|
||||
|
||||
function makeUploadRequest (options: CommonRequestParams & {
|
||||
export function makeUploadRequest (options: CommonRequestParams & {
|
||||
method?: 'POST' | 'PUT'
|
||||
|
||||
fields: { [ fieldName: string ]: any }
|
||||
|
@ -119,7 +141,7 @@ function makeUploadRequest (options: CommonRequestParams & {
|
|||
return req
|
||||
}
|
||||
|
||||
function makePostBodyRequest (options: CommonRequestParams & {
|
||||
export function makePostBodyRequest (options: CommonRequestParams & {
|
||||
fields?: { [ fieldName: string ]: any }
|
||||
}) {
|
||||
const req = request(options.url).post(options.path)
|
||||
|
@ -128,7 +150,7 @@ function makePostBodyRequest (options: CommonRequestParams & {
|
|||
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||
}
|
||||
|
||||
function makePutBodyRequest (options: {
|
||||
export function makePutBodyRequest (options: {
|
||||
url: string
|
||||
path: string
|
||||
token?: string
|
||||
|
@ -142,21 +164,35 @@ function makePutBodyRequest (options: {
|
|||
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||
}
|
||||
|
||||
function decodeQueryString (path: string) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function getRedirectionUrl (url: string) {
|
||||
const res = await makeRawRequest({
|
||||
url,
|
||||
redirects: 0,
|
||||
expectedStatus: HttpStatusCode.FOUND_302
|
||||
})
|
||||
|
||||
return res.headers['location']
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function decodeQueryString (path: string) {
|
||||
return decode(path.split('?')[1])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function unwrapBody <T> (test: request.Test): Promise<T> {
|
||||
export function unwrapBody <T> (test: request.Test): Promise<T> {
|
||||
return test.then(res => res.body)
|
||||
}
|
||||
|
||||
function unwrapText (test: request.Test): Promise<string> {
|
||||
export function unwrapText (test: request.Test): Promise<string> {
|
||||
return test.then(res => res.text)
|
||||
}
|
||||
|
||||
function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
|
||||
export function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
|
||||
return test.then(res => {
|
||||
if (res.body instanceof Buffer) {
|
||||
try {
|
||||
|
@ -180,28 +216,12 @@ function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
|
|||
})
|
||||
}
|
||||
|
||||
function unwrapTextOrDecode (test: request.Test): Promise<string> {
|
||||
export function unwrapTextOrDecode (test: request.Test): Promise<string> {
|
||||
return test.then(res => res.text || new TextDecoder().decode(res.body))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
makeHTMLRequest,
|
||||
makeGetRequest,
|
||||
decodeQueryString,
|
||||
makeUploadRequest,
|
||||
makePostBodyRequest,
|
||||
makePutBodyRequest,
|
||||
makeDeleteRequest,
|
||||
makeRawRequest,
|
||||
makeActivityPubGetRequest,
|
||||
unwrapBody,
|
||||
unwrapTextOrDecode,
|
||||
unwrapBodyOrDecodeToJSON,
|
||||
unwrapText
|
||||
}
|
||||
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildRequest (req: request.Test, options: CommonRequestParams) {
|
||||
|
|
|
@ -46,15 +46,15 @@ export class ConfigCommand extends AbstractCommand {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
disableImports () {
|
||||
return this.setImportsEnabled(false)
|
||||
disableVideoImports () {
|
||||
return this.setVideoImportsEnabled(false)
|
||||
}
|
||||
|
||||
enableImports () {
|
||||
return this.setImportsEnabled(true)
|
||||
enableVideoImports () {
|
||||
return this.setVideoImportsEnabled(true)
|
||||
}
|
||||
|
||||
private setImportsEnabled (enabled: boolean) {
|
||||
private setVideoImportsEnabled (enabled: boolean) {
|
||||
return this.updateExistingSubConfig({
|
||||
newConfig: {
|
||||
import: {
|
||||
|
@ -118,6 +118,74 @@ export class ConfigCommand extends AbstractCommand {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enableAutoBlacklist () {
|
||||
return this.setAutoblacklistEnabled(true)
|
||||
}
|
||||
|
||||
disableAutoBlacklist () {
|
||||
return this.setAutoblacklistEnabled(false)
|
||||
}
|
||||
|
||||
private setAutoblacklistEnabled (enabled: boolean) {
|
||||
return this.updateExistingSubConfig({
|
||||
newConfig: {
|
||||
autoBlacklist: {
|
||||
videos: {
|
||||
ofUsers: {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enableUserImport () {
|
||||
return this.setUserImportEnabled(true)
|
||||
}
|
||||
|
||||
disableUserImport () {
|
||||
return this.setUserImportEnabled(false)
|
||||
}
|
||||
|
||||
private setUserImportEnabled (enabled: boolean) {
|
||||
return this.updateExistingSubConfig({
|
||||
newConfig: {
|
||||
import: {
|
||||
users: {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enableUserExport () {
|
||||
return this.setUserExportEnabled(true)
|
||||
}
|
||||
|
||||
disableUserExport () {
|
||||
return this.setUserExportEnabled(false)
|
||||
}
|
||||
|
||||
private setUserExportEnabled (enabled: boolean) {
|
||||
return this.updateExistingSubConfig({
|
||||
newConfig: {
|
||||
export: {
|
||||
users: {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enableLive (options: {
|
||||
allowReplay?: boolean
|
||||
transcoding?: boolean
|
||||
|
@ -552,6 +620,16 @@ export class ConfigCommand extends AbstractCommand {
|
|||
videoChannelSynchronization: {
|
||||
enabled: false,
|
||||
maxPerUser: 10
|
||||
},
|
||||
users: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
export: {
|
||||
users: {
|
||||
enabled: true,
|
||||
maxUserVideoQuota: 5242881,
|
||||
exportExpiration: 1000 * 3600
|
||||
}
|
||||
},
|
||||
trending: {
|
||||
|
|
|
@ -17,12 +17,14 @@ import { SocketIOCommand } from '../socket/index.js'
|
|||
import {
|
||||
AccountsCommand,
|
||||
BlocklistCommand,
|
||||
UserExportsCommand,
|
||||
LoginCommand,
|
||||
NotificationsCommand,
|
||||
RegistrationsCommand,
|
||||
SubscriptionsCommand,
|
||||
TwoFactorCommand,
|
||||
UsersCommand
|
||||
UsersCommand,
|
||||
UserImportsCommand
|
||||
} from '../users/index.js'
|
||||
import {
|
||||
BlacklistCommand,
|
||||
|
@ -33,7 +35,7 @@ import {
|
|||
ChaptersCommand,
|
||||
CommentsCommand,
|
||||
HistoryCommand,
|
||||
ImportsCommand,
|
||||
VideoImportsCommand,
|
||||
LiveCommand,
|
||||
PlaylistsCommand,
|
||||
ServicesCommand,
|
||||
|
@ -90,7 +92,6 @@ export class PeerTubeServer {
|
|||
user?: {
|
||||
username: string
|
||||
password: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
channel?: VideoChannel
|
||||
|
@ -134,7 +135,7 @@ export class PeerTubeServer {
|
|||
changeOwnership?: ChangeOwnershipCommand
|
||||
playlists?: PlaylistsCommand
|
||||
history?: HistoryCommand
|
||||
imports?: ImportsCommand
|
||||
videoImports?: VideoImportsCommand
|
||||
channelSyncs?: ChannelSyncsCommand
|
||||
streamingPlaylists?: StreamingPlaylistsCommand
|
||||
channels?: ChannelsCommand
|
||||
|
@ -155,6 +156,9 @@ export class PeerTubeServer {
|
|||
storyboard?: StoryboardCommand
|
||||
chapters?: ChaptersCommand
|
||||
|
||||
userImports?: UserImportsCommand
|
||||
userExports?: UserExportsCommand
|
||||
|
||||
runners?: RunnersCommand
|
||||
runnerRegistrationTokens?: RunnerRegistrationTokensCommand
|
||||
runnerJobs?: RunnerJobsCommand
|
||||
|
@ -426,7 +430,7 @@ export class PeerTubeServer {
|
|||
this.changeOwnership = new ChangeOwnershipCommand(this)
|
||||
this.playlists = new PlaylistsCommand(this)
|
||||
this.history = new HistoryCommand(this)
|
||||
this.imports = new ImportsCommand(this)
|
||||
this.videoImports = new VideoImportsCommand(this)
|
||||
this.channelSyncs = new ChannelSyncsCommand(this)
|
||||
this.streamingPlaylists = new StreamingPlaylistsCommand(this)
|
||||
this.channels = new ChannelsCommand(this)
|
||||
|
@ -446,6 +450,9 @@ export class PeerTubeServer {
|
|||
this.storyboard = new StoryboardCommand(this)
|
||||
this.chapters = new ChaptersCommand(this)
|
||||
|
||||
this.userExports = new UserExportsCommand(this)
|
||||
this.userImports = new UserImportsCommand(this)
|
||||
|
||||
this.runners = new RunnersCommand(this)
|
||||
this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this)
|
||||
this.runnerJobs = new RunnerJobsCommand(this)
|
||||
|
|
|
@ -21,7 +21,7 @@ function createMultipleServers (totalServers: number, configOverride?: object, o
|
|||
}
|
||||
|
||||
function killallServers (servers: PeerTubeServer[]) {
|
||||
return Promise.all(servers.map(s => s.kill()))
|
||||
return Promise.all(servers.filter(s => !!s).map(s => s.kill()))
|
||||
}
|
||||
|
||||
async function cleanupTests (servers: PeerTubeServer[]) {
|
||||
|
@ -33,6 +33,8 @@ async function cleanupTests (servers: PeerTubeServer[]) {
|
|||
|
||||
let p: Promise<any>[] = []
|
||||
for (const server of servers) {
|
||||
if (!server) continue
|
||||
|
||||
p = p.concat(server.servers.cleanupTests())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
|
||||
|
||||
import { isAbsolute } from 'path'
|
||||
import { HttpStatusCodeType } from '@peertube/peertube-models'
|
||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||
import { buildAbsoluteFixturePath, getFileSize } from '@peertube/peertube-node-utils'
|
||||
import {
|
||||
makeDeleteRequest,
|
||||
makeGetRequest,
|
||||
|
@ -10,8 +11,12 @@ import {
|
|||
unwrapBody,
|
||||
unwrapText
|
||||
} from '../requests/requests.js'
|
||||
import { expect } from 'chai'
|
||||
import got, { Response as GotResponse } from 'got'
|
||||
import { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models'
|
||||
|
||||
import type { PeerTubeServer } from '../server/server.js'
|
||||
import { createReadStream } from 'fs'
|
||||
|
||||
export interface OverrideCommandOptions {
|
||||
token?: string
|
||||
|
@ -48,7 +53,7 @@ interface InternalDeleteCommandOptions extends InternalCommonCommandOptions {
|
|||
rawQuery?: string
|
||||
}
|
||||
|
||||
abstract class AbstractCommand {
|
||||
export abstract class AbstractCommand {
|
||||
|
||||
constructor (
|
||||
protected server: PeerTubeServer
|
||||
|
@ -218,8 +223,221 @@ abstract class AbstractCommand {
|
|||
? { 'x-peertube-video-password': videoPassword }
|
||||
: undefined
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
protected async buildResumeUpload <T> (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
|
||||
fixture: string
|
||||
attaches?: Record<string, string>
|
||||
fields?: Record<string, any>
|
||||
|
||||
completedExpectedStatus?: HttpStatusCodeType // When the upload is finished
|
||||
}): Promise<T> {
|
||||
const { path, fixture, expectedStatus = HttpStatusCode.OK_200, completedExpectedStatus } = options
|
||||
|
||||
let size = 0
|
||||
let videoFilePath: string
|
||||
let mimetype = 'video/mp4'
|
||||
|
||||
if (fixture) {
|
||||
videoFilePath = buildAbsoluteFixturePath(fixture)
|
||||
size = await getFileSize(videoFilePath)
|
||||
|
||||
if (videoFilePath.endsWith('.mkv')) {
|
||||
mimetype = 'video/x-matroska'
|
||||
} else if (videoFilePath.endsWith('.webm')) {
|
||||
mimetype = 'video/webm'
|
||||
} else if (videoFilePath.endsWith('.zip')) {
|
||||
mimetype = 'application/zip'
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AbstractCommand
|
||||
// Do not check status automatically, we'll check it manually
|
||||
const initializeSessionRes = await this.prepareResumableUpload({
|
||||
...options,
|
||||
|
||||
path,
|
||||
expectedStatus: null,
|
||||
|
||||
size,
|
||||
mimetype
|
||||
})
|
||||
const initStatus = initializeSessionRes.status
|
||||
|
||||
if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) {
|
||||
const locationHeader = initializeSessionRes.header['location']
|
||||
expect(locationHeader).to.not.be.undefined
|
||||
|
||||
const pathUploadId = locationHeader.split('?')[1]
|
||||
|
||||
const result = await this.sendResumableChunks({
|
||||
...options,
|
||||
|
||||
path,
|
||||
pathUploadId,
|
||||
videoFilePath,
|
||||
size,
|
||||
expectedStatus: completedExpectedStatus
|
||||
})
|
||||
|
||||
if (result.statusCode === HttpStatusCode.OK_200) {
|
||||
await this.endResumableUpload({
|
||||
...options,
|
||||
|
||||
expectedStatus: HttpStatusCode.NO_CONTENT_204,
|
||||
path,
|
||||
pathUploadId
|
||||
})
|
||||
}
|
||||
|
||||
return result.body as T
|
||||
}
|
||||
|
||||
const expectedInitStatus = expectedStatus === HttpStatusCode.OK_200
|
||||
? HttpStatusCode.CREATED_201
|
||||
: expectedStatus
|
||||
|
||||
expect(initStatus).to.equal(expectedInitStatus)
|
||||
|
||||
return initializeSessionRes.body.video || initializeSessionRes.body
|
||||
}
|
||||
|
||||
protected async prepareResumableUpload (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
|
||||
fixture: string
|
||||
size: number
|
||||
mimetype: string
|
||||
|
||||
attaches?: Record<string, string>
|
||||
fields?: Record<string, any>
|
||||
|
||||
originalName?: string
|
||||
lastModified?: number
|
||||
}) {
|
||||
const { path, attaches = {}, fields = {}, originalName, lastModified, fixture, size, mimetype } = options
|
||||
|
||||
const uploadOptions = {
|
||||
...options,
|
||||
|
||||
path,
|
||||
headers: {
|
||||
'X-Upload-Content-Type': mimetype,
|
||||
'X-Upload-Content-Length': size.toString()
|
||||
},
|
||||
fields: {
|
||||
filename: fixture,
|
||||
originalName,
|
||||
lastModified,
|
||||
|
||||
...fields
|
||||
},
|
||||
|
||||
// Fixture will be sent later
|
||||
attaches,
|
||||
implicitToken: true,
|
||||
|
||||
defaultExpectedStatus: null
|
||||
}
|
||||
|
||||
if (Object.keys(attaches).length === 0) return this.postBodyRequest(uploadOptions)
|
||||
|
||||
return this.postUploadRequest(uploadOptions)
|
||||
}
|
||||
|
||||
protected async sendResumableChunks <T> (options: OverrideCommandOptions & {
|
||||
pathUploadId: string
|
||||
path: string
|
||||
videoFilePath: string
|
||||
size: number
|
||||
contentLength?: number
|
||||
contentRangeBuilder?: (start: number, chunk: any) => string
|
||||
digestBuilder?: (chunk: any) => string
|
||||
}) {
|
||||
const {
|
||||
path,
|
||||
pathUploadId,
|
||||
videoFilePath,
|
||||
size,
|
||||
contentLength,
|
||||
contentRangeBuilder,
|
||||
digestBuilder,
|
||||
expectedStatus = HttpStatusCode.OK_200
|
||||
} = options
|
||||
|
||||
let start = 0
|
||||
|
||||
const token = this.buildCommonRequestToken({ ...options, implicitToken: true })
|
||||
|
||||
const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 })
|
||||
const server = this.server
|
||||
return new Promise<GotResponse<T>>((resolve, reject) => {
|
||||
readable.on('data', async function onData (chunk) {
|
||||
try {
|
||||
readable.pause()
|
||||
|
||||
const byterangeStart = start + chunk.length - 1
|
||||
|
||||
const headers = {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Range': contentRangeBuilder
|
||||
? contentRangeBuilder(start, chunk)
|
||||
: `bytes ${start}-${byterangeStart}/${size}`,
|
||||
'Content-Length': contentLength ? contentLength + '' : chunk.length + ''
|
||||
}
|
||||
|
||||
if (digestBuilder) {
|
||||
Object.assign(headers, { digest: digestBuilder(chunk) })
|
||||
}
|
||||
|
||||
const res = await got<T>({
|
||||
url: new URL(path + '?' + pathUploadId, server.url).toString(),
|
||||
method: 'put',
|
||||
headers,
|
||||
body: chunk,
|
||||
responseType: 'json',
|
||||
throwHttpErrors: false
|
||||
})
|
||||
|
||||
start += chunk.length
|
||||
|
||||
// Last request, check final status
|
||||
if (byterangeStart + 1 === size) {
|
||||
if (res.statusCode === expectedStatus) {
|
||||
return resolve(res)
|
||||
}
|
||||
|
||||
if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) {
|
||||
readable.off('data', onData)
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const message = `Incorrect transient behaviour sending intermediary chunks. Status code is ${res.statusCode} instead of ${expectedStatus}`
|
||||
return reject(new Error(message))
|
||||
}
|
||||
}
|
||||
|
||||
readable.resume()
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
protected endResumableUpload (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
pathUploadId: string
|
||||
}) {
|
||||
return this.deleteRequest({
|
||||
...options,
|
||||
|
||||
path: options.path,
|
||||
rawQuery: options.pathUploadId,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
export * from './accounts-command.js'
|
||||
export * from './accounts.js'
|
||||
export * from './blocklist-command.js'
|
||||
export * from './login.js'
|
||||
export * from './login-command.js'
|
||||
export * from './login.js'
|
||||
export * from './notifications-command.js'
|
||||
export * from './registrations-command.js'
|
||||
export * from './subscriptions-command.js'
|
||||
export * from './two-factor-command.js'
|
||||
export * from './user-exports-command.js'
|
||||
export * from './user-imports-command.js'
|
||||
export * from './users-command.js'
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
import { PeerTubeServer } from '../server/server.js'
|
||||
|
||||
function setAccessTokensToServers (servers: PeerTubeServer[]) {
|
||||
const tasks: Promise<any>[] = []
|
||||
export function setAccessTokensToServers (servers: PeerTubeServer[]) {
|
||||
return Promise.all(
|
||||
servers.map(async server => {
|
||||
const token = await server.login.getAccessToken()
|
||||
|
||||
for (const server of servers) {
|
||||
const p = server.login.getAccessToken()
|
||||
.then(t => { server.accessToken = t })
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
setAccessTokensToServers
|
||||
server.accessToken = token
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
77
packages/server-commands/src/users/user-exports-command.ts
Normal file
77
packages/server-commands/src/users/user-exports-command.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { HttpStatusCode, ResultList, UserExport, UserExportRequestResult, UserExportState } from '@peertube/peertube-models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
||||
import { wait } from '@peertube/peertube-core-utils'
|
||||
import { unwrapBody } from '../requests/requests.js'
|
||||
|
||||
export class UserExportsCommand extends AbstractCommand {
|
||||
|
||||
request (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
withVideoFiles: boolean
|
||||
}) {
|
||||
const { userId, withVideoFiles } = options
|
||||
|
||||
return unwrapBody<UserExportRequestResult>(this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path: `/api/v1/users/${userId}/exports/request`,
|
||||
fields: { withVideoFiles },
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
}))
|
||||
}
|
||||
|
||||
async waitForCreation (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
}) {
|
||||
const { userId } = options
|
||||
|
||||
while (true) {
|
||||
const { data } = await this.list({ ...options, userId })
|
||||
|
||||
if (data.some(e => e.state.id === UserExportState.COMPLETED)) break
|
||||
|
||||
await wait(250)
|
||||
}
|
||||
}
|
||||
|
||||
list (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
}) {
|
||||
const { userId } = options
|
||||
|
||||
return this.getRequestBody<ResultList<UserExport>>({
|
||||
...options,
|
||||
|
||||
path: `/api/v1/users/${userId}/exports`,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
async deleteAllArchives (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
}) {
|
||||
const { data } = await this.list(options)
|
||||
|
||||
for (const { id } of data) {
|
||||
await this.delete({ ...options, exportId: id })
|
||||
}
|
||||
}
|
||||
|
||||
delete (options: OverrideCommandOptions & {
|
||||
exportId: number
|
||||
userId: number
|
||||
}) {
|
||||
const { userId, exportId } = options
|
||||
|
||||
return this.deleteRequest({
|
||||
...options,
|
||||
|
||||
path: `/api/v1/users/${userId}/exports/${exportId}`,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
}
|
31
packages/server-commands/src/users/user-imports-command.ts
Normal file
31
packages/server-commands/src/users/user-imports-command.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { HttpStatusCode, HttpStatusCodeType, UserImport, UserImportUploadResult } from '@peertube/peertube-models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
||||
|
||||
export class UserImportsCommand extends AbstractCommand {
|
||||
|
||||
importArchive (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
fixture: string
|
||||
completedExpectedStatus?: HttpStatusCodeType
|
||||
}) {
|
||||
return this.buildResumeUpload<UserImportUploadResult>({
|
||||
...options,
|
||||
|
||||
path: `/api/v1/users/${options.userId}/imports/import-resumable`,
|
||||
fixture: options.fixture,
|
||||
completedExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
getLatestImport (options: OverrideCommandOptions & {
|
||||
userId: number
|
||||
}) {
|
||||
return this.getRequestBody<UserImport>({
|
||||
...options,
|
||||
|
||||
path: `/api/v1/users/${options.userId}/imports/latest`,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ export * from './chapters-command.js'
|
|||
export * from './channel-syncs-command.js'
|
||||
export * from './comments-command.js'
|
||||
export * from './history-command.js'
|
||||
export * from './imports-command.js'
|
||||
export * from './video-imports-command.js'
|
||||
export * from './live-command.js'
|
||||
export * from './live.js'
|
||||
export * from './playlists-command.js'
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
VideoPlaylistElementCreate,
|
||||
VideoPlaylistElementCreateResult,
|
||||
VideoPlaylistElementUpdate,
|
||||
VideoPlaylistPrivacy,
|
||||
VideoPlaylistPrivacyType,
|
||||
VideoPlaylistReorder,
|
||||
VideoPlaylistType_Type,
|
||||
VideoPlaylistUpdate
|
||||
|
@ -156,6 +158,27 @@ export class PlaylistsCommand extends AbstractCommand {
|
|||
return body.videoPlaylist
|
||||
}
|
||||
|
||||
async quickCreate (options: OverrideCommandOptions & {
|
||||
displayName: string
|
||||
privacy?: VideoPlaylistPrivacyType
|
||||
}) {
|
||||
const { displayName, privacy = VideoPlaylistPrivacy.PUBLIC } = options
|
||||
|
||||
const { videoChannels } = await this.server.users.getMyInfo({ token: options.token })
|
||||
|
||||
return this.create({
|
||||
...options,
|
||||
|
||||
attributes: {
|
||||
displayName,
|
||||
privacy,
|
||||
videoChannelId: privacy === VideoPlaylistPrivacy.PUBLIC
|
||||
? videoChannels[0].id
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
update (options: OverrideCommandOptions & {
|
||||
attributes: VideoPlaylistUpdate
|
||||
playlistId: number | string
|
||||
|
|
|
@ -2,7 +2,7 @@ import { HttpStatusCode, ResultList, VideoImport, VideoImportCreate } from '@pee
|
|||
import { unwrapBody } from '../requests/index.js'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
||||
|
||||
export class ImportsCommand extends AbstractCommand {
|
||||
export class VideoImportsCommand extends AbstractCommand {
|
||||
|
||||
importVideo (options: OverrideCommandOptions & {
|
||||
attributes: (VideoImportCreate | { torrentfile?: string, previewfile?: string, thumbnailfile?: string })
|
|
@ -1,9 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { createReadStream } from 'fs'
|
||||
import { stat } from 'fs/promises'
|
||||
import got, { Response as GotResponse } from 'got'
|
||||
import validator from 'validator'
|
||||
import { getAllPrivacies, omit, pick, wait } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
|
@ -429,7 +425,13 @@ export class VideosCommand extends AbstractCommand {
|
|||
|
||||
const created = mode === 'legacy'
|
||||
? await this.buildLegacyUpload({ ...options, attributes })
|
||||
: await this.buildResumeUpload({ ...options, path: '/api/v1/videos/upload-resumable', attributes })
|
||||
: await this.buildResumeVideoUpload({
|
||||
...options,
|
||||
path: '/api/v1/videos/upload-resumable',
|
||||
fixture: attributes.fixture,
|
||||
attaches: this.buildUploadAttaches(attributes, false),
|
||||
fields: this.buildUploadFields(attributes)
|
||||
})
|
||||
|
||||
// Wait torrent generation
|
||||
const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 })
|
||||
|
@ -456,231 +458,26 @@ export class VideosCommand extends AbstractCommand {
|
|||
|
||||
path,
|
||||
fields: this.buildUploadFields(options.attributes),
|
||||
attaches: this.buildUploadAttaches(options.attributes),
|
||||
attaches: this.buildUploadAttaches(options.attributes, true),
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})).then(body => body.video || body as any)
|
||||
}
|
||||
|
||||
async buildResumeUpload (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
attributes: { fixture?: string } & { [id: string]: any }
|
||||
completedExpectedStatus?: HttpStatusCodeType // When the upload is finished
|
||||
}): Promise<VideoCreateResult> {
|
||||
const { path, attributes, expectedStatus = HttpStatusCode.OK_200, completedExpectedStatus } = options
|
||||
|
||||
let size = 0
|
||||
let videoFilePath: string
|
||||
let mimetype = 'video/mp4'
|
||||
|
||||
if (attributes.fixture) {
|
||||
videoFilePath = buildAbsoluteFixturePath(attributes.fixture)
|
||||
size = (await stat(videoFilePath)).size
|
||||
|
||||
if (videoFilePath.endsWith('.mkv')) {
|
||||
mimetype = 'video/x-matroska'
|
||||
} else if (videoFilePath.endsWith('.webm')) {
|
||||
mimetype = 'video/webm'
|
||||
}
|
||||
}
|
||||
|
||||
// Do not check status automatically, we'll check it manually
|
||||
const initializeSessionRes = await this.prepareResumableUpload({
|
||||
...options,
|
||||
|
||||
path,
|
||||
expectedStatus: null,
|
||||
attributes,
|
||||
size,
|
||||
mimetype
|
||||
})
|
||||
const initStatus = initializeSessionRes.status
|
||||
|
||||
if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) {
|
||||
const locationHeader = initializeSessionRes.header['location']
|
||||
expect(locationHeader).to.not.be.undefined
|
||||
|
||||
const pathUploadId = locationHeader.split('?')[1]
|
||||
|
||||
const result = await this.sendResumableChunks({
|
||||
...options,
|
||||
|
||||
path,
|
||||
pathUploadId,
|
||||
videoFilePath,
|
||||
size,
|
||||
expectedStatus: completedExpectedStatus
|
||||
})
|
||||
|
||||
if (result.statusCode === HttpStatusCode.OK_200) {
|
||||
await this.endResumableUpload({
|
||||
...options,
|
||||
|
||||
expectedStatus: HttpStatusCode.NO_CONTENT_204,
|
||||
path,
|
||||
pathUploadId
|
||||
})
|
||||
}
|
||||
|
||||
return result.body?.video || result.body as any
|
||||
}
|
||||
|
||||
const expectedInitStatus = expectedStatus === HttpStatusCode.OK_200
|
||||
? HttpStatusCode.CREATED_201
|
||||
: expectedStatus
|
||||
|
||||
expect(initStatus).to.equal(expectedInitStatus)
|
||||
|
||||
return initializeSessionRes.body.video || initializeSessionRes.body
|
||||
}
|
||||
|
||||
async prepareResumableUpload (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
attributes: { fixture?: string } & { [id: string]: any }
|
||||
size: number
|
||||
mimetype: string
|
||||
|
||||
originalName?: string
|
||||
lastModified?: number
|
||||
}) {
|
||||
const { path, attributes, originalName, lastModified, size, mimetype } = options
|
||||
|
||||
const attaches = this.buildUploadAttaches(omit(options.attributes, [ 'fixture' ]))
|
||||
|
||||
const uploadOptions = {
|
||||
...options,
|
||||
|
||||
path,
|
||||
headers: {
|
||||
'X-Upload-Content-Type': mimetype,
|
||||
'X-Upload-Content-Length': size.toString()
|
||||
},
|
||||
fields: {
|
||||
filename: attributes.fixture,
|
||||
originalName,
|
||||
lastModified,
|
||||
|
||||
...this.buildUploadFields(options.attributes)
|
||||
},
|
||||
|
||||
// Fixture will be sent later
|
||||
attaches: this.buildUploadAttaches(omit(options.attributes, [ 'fixture' ])),
|
||||
implicitToken: true,
|
||||
|
||||
defaultExpectedStatus: null
|
||||
}
|
||||
|
||||
if (Object.keys(attaches).length === 0) return this.postBodyRequest(uploadOptions)
|
||||
|
||||
return this.postUploadRequest(uploadOptions)
|
||||
}
|
||||
|
||||
sendResumableChunks (options: OverrideCommandOptions & {
|
||||
pathUploadId: string
|
||||
path: string
|
||||
videoFilePath: string
|
||||
size: number
|
||||
contentLength?: number
|
||||
contentRangeBuilder?: (start: number, chunk: any) => string
|
||||
digestBuilder?: (chunk: any) => string
|
||||
}) {
|
||||
const {
|
||||
path,
|
||||
pathUploadId,
|
||||
videoFilePath,
|
||||
size,
|
||||
contentLength,
|
||||
contentRangeBuilder,
|
||||
digestBuilder,
|
||||
expectedStatus = HttpStatusCode.OK_200
|
||||
} = options
|
||||
|
||||
let start = 0
|
||||
|
||||
const token = this.buildCommonRequestToken({ ...options, implicitToken: true })
|
||||
|
||||
const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 })
|
||||
const server = this.server
|
||||
return new Promise<GotResponse<{ video: VideoCreateResult }>>((resolve, reject) => {
|
||||
readable.on('data', async function onData (chunk) {
|
||||
try {
|
||||
readable.pause()
|
||||
|
||||
const byterangeStart = start + chunk.length - 1
|
||||
|
||||
const headers = {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Range': contentRangeBuilder
|
||||
? contentRangeBuilder(start, chunk)
|
||||
: `bytes ${start}-${byterangeStart}/${size}`,
|
||||
'Content-Length': contentLength ? contentLength + '' : chunk.length + ''
|
||||
}
|
||||
|
||||
if (digestBuilder) {
|
||||
Object.assign(headers, { digest: digestBuilder(chunk) })
|
||||
}
|
||||
|
||||
const res = await got<{ video: VideoCreateResult }>({
|
||||
url: new URL(path + '?' + pathUploadId, server.url).toString(),
|
||||
method: 'put',
|
||||
headers,
|
||||
body: chunk,
|
||||
responseType: 'json',
|
||||
throwHttpErrors: false
|
||||
})
|
||||
|
||||
start += chunk.length
|
||||
|
||||
// Last request, check final status
|
||||
if (byterangeStart + 1 === size) {
|
||||
if (res.statusCode === expectedStatus) {
|
||||
return resolve(res)
|
||||
}
|
||||
|
||||
if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) {
|
||||
readable.off('data', onData)
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const message = `Incorrect transient behaviour sending intermediary chunks. Status code is ${res.statusCode} instead of ${expectedStatus}`
|
||||
return reject(new Error(message))
|
||||
}
|
||||
}
|
||||
|
||||
readable.resume()
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
endResumableUpload (options: OverrideCommandOptions & {
|
||||
path: string
|
||||
pathUploadId: string
|
||||
}) {
|
||||
return this.deleteRequest({
|
||||
...options,
|
||||
|
||||
path: options.path,
|
||||
rawQuery: options.pathUploadId,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
quickUpload (options: OverrideCommandOptions & {
|
||||
name: string
|
||||
nsfw?: boolean
|
||||
privacy?: VideoPrivacyType
|
||||
fixture?: string
|
||||
videoPasswords?: string[]
|
||||
channelId?: number
|
||||
}) {
|
||||
const attributes: VideoEdit = { name: options.name }
|
||||
if (options.nsfw) attributes.nsfw = options.nsfw
|
||||
if (options.privacy) attributes.privacy = options.privacy
|
||||
if (options.fixture) attributes.fixture = options.fixture
|
||||
if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords
|
||||
if (options.channelId) attributes.channelId = options.channelId
|
||||
|
||||
return this.upload({ ...options, attributes })
|
||||
}
|
||||
|
@ -713,7 +510,7 @@ export class VideosCommand extends AbstractCommand {
|
|||
...options,
|
||||
|
||||
path: '/api/v1/videos/' + options.videoId + '/source/replace-resumable',
|
||||
attributes: { fixture: options.fixture }
|
||||
fixture: options.fixture
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -813,19 +610,38 @@ export class VideosCommand extends AbstractCommand {
|
|||
])
|
||||
}
|
||||
|
||||
private buildUploadFields (attributes: VideoEdit) {
|
||||
buildUploadFields (attributes: VideoEdit) {
|
||||
return omit(attributes, [ 'fixture', 'thumbnailfile', 'previewfile' ])
|
||||
}
|
||||
|
||||
private buildUploadAttaches (attributes: VideoEdit) {
|
||||
buildUploadAttaches (attributes: VideoEdit, includeFixture: boolean) {
|
||||
const attaches: { [ name: string ]: string } = {}
|
||||
|
||||
for (const key of [ 'thumbnailfile', 'previewfile' ]) {
|
||||
if (attributes[key]) attaches[key] = buildAbsoluteFixturePath(attributes[key])
|
||||
}
|
||||
|
||||
if (attributes.fixture) attaches.videofile = buildAbsoluteFixturePath(attributes.fixture)
|
||||
if (includeFixture && attributes.fixture) attaches.videofile = buildAbsoluteFixturePath(attributes.fixture)
|
||||
|
||||
return attaches
|
||||
}
|
||||
|
||||
// Make these methods public, needed by some offensive tests
|
||||
sendResumableVideoChunks (options: Parameters<AbstractCommand['sendResumableChunks']>[0]) {
|
||||
return super.sendResumableChunks<{ video: VideoCreateResult }>(options)
|
||||
}
|
||||
|
||||
async buildResumeVideoUpload (options: Parameters<AbstractCommand['buildResumeUpload']>[0]) {
|
||||
const result = await super.buildResumeUpload<{ video: VideoCreateResult }>(options)
|
||||
|
||||
return result?.video || undefined
|
||||
}
|
||||
|
||||
prepareVideoResumableUpload (options: Parameters<AbstractCommand['prepareResumableUpload']>[0]) {
|
||||
return super.prepareResumableUpload(options)
|
||||
}
|
||||
|
||||
endVideoResumableUpload (options: Parameters<AbstractCommand['endResumableUpload']>[0]) {
|
||||
return super.endResumableUpload(options)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
||||
|
|
BIN
packages/tests/fixtures/custom-thumbnail-from-preview.jpg
vendored
Normal file
BIN
packages/tests/fixtures/custom-thumbnail-from-preview.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
BIN
packages/tests/fixtures/export-bad-structure.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-bad-structure.zip
vendored
Normal file
Binary file not shown.
BIN
packages/tests/fixtures/export-bad-video-file.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-bad-video-file.zip
vendored
Normal file
Binary file not shown.
BIN
packages/tests/fixtures/export-bad-video.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-bad-video.zip
vendored
Normal file
Binary file not shown.
BIN
packages/tests/fixtures/export-with-files.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-with-files.zip
vendored
Normal file
Binary file not shown.
BIN
packages/tests/fixtures/export-without-files.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-without-files.zip
vendored
Normal file
Binary file not shown.
BIN
packages/tests/fixtures/export-without-videos.zip
vendored
Normal file
BIN
packages/tests/fixtures/export-without-videos.zip
vendored
Normal file
Binary file not shown.
|
@ -212,6 +212,16 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
|
|||
}
|
||||
})
|
||||
|
||||
registerHook({
|
||||
target: 'filter:api.video.user-import.accept.result',
|
||||
handler: ({ accepted }, { videoBody }) => {
|
||||
if (!accepted) return { accepted: false }
|
||||
if (videoBody.name === 'video 1') return { accepted: false, errorMessage: 'bad word' }
|
||||
|
||||
return { accepted: true }
|
||||
}
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
registerHook({
|
||||
|
@ -402,7 +412,8 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
|
|||
'filter:api.video.upload.video-attribute.result',
|
||||
'filter:api.video.import-url.video-attribute.result',
|
||||
'filter:api.video.import-torrent.video-attribute.result',
|
||||
'filter:api.video.live.video-attribute.result'
|
||||
'filter:api.video.live.video-attribute.result',
|
||||
'filter:api.video.user-import.video-attribute.result'
|
||||
]) {
|
||||
registerHook({
|
||||
target,
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('Test videos import in a channel API validator', function () {
|
|||
await setAccessTokensToServers([ server ])
|
||||
await setDefaultVideoChannel([ server ])
|
||||
|
||||
await server.config.enableImports()
|
||||
await server.config.enableVideoImports()
|
||||
await server.config.enableChannelSync()
|
||||
|
||||
const userCreds = {
|
||||
|
@ -68,7 +68,7 @@ describe('Test videos import in a channel API validator', function () {
|
|||
|
||||
it('Should fail when HTTP upload is disabled', async function () {
|
||||
await server.config.disableChannelSync()
|
||||
await server.config.disableImports()
|
||||
await server.config.disableVideoImports()
|
||||
|
||||
await command.importVideos({
|
||||
channelName: server.store.channel.name,
|
||||
|
@ -77,7 +77,7 @@ describe('Test videos import in a channel API validator', function () {
|
|||
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
||||
})
|
||||
|
||||
await server.config.enableImports()
|
||||
await server.config.enableVideoImports()
|
||||
})
|
||||
|
||||
it('Should fail when externalChannelUrl is not provided', async function () {
|
||||
|
|
|
@ -192,6 +192,16 @@ describe('Test config API validators', function () {
|
|||
videoChannelSynchronization: {
|
||||
enabled: false,
|
||||
maxPerUser: 10
|
||||
},
|
||||
users: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
export: {
|
||||
users: {
|
||||
enabled: false,
|
||||
maxUserVideoQuota: 40,
|
||||
exportExpiration: 10
|
||||
}
|
||||
},
|
||||
trending: {
|
||||
|
|
|
@ -8,6 +8,8 @@ import './contact-form.js'
|
|||
import './custom-pages.js'
|
||||
import './debug.js'
|
||||
import './follows.js'
|
||||
import './user-export.js'
|
||||
import './user-import.js.js'
|
||||
import './jobs.js'
|
||||
import './live.js'
|
||||
import './logs.js'
|
||||
|
|
|
@ -75,13 +75,13 @@ describe('Test upload quota', function () {
|
|||
channelId: server.store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
await server.imports.importVideo({ attributes: { ...baseAttributes, targetUrl: FIXTURE_URLS.goodVideo } })
|
||||
await server.imports.importVideo({ attributes: { ...baseAttributes, magnetUri: FIXTURE_URLS.magnet } })
|
||||
await server.imports.importVideo({ attributes: { ...baseAttributes, torrentfile: 'video-720p.torrent' as any } })
|
||||
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.imports.getMyVideoImports()
|
||||
const { total, data: videoImports } = await server.videoImports.getMyVideoImports()
|
||||
expect(total).to.equal(3)
|
||||
|
||||
expect(videoImports).to.have.lengthOf(3)
|
||||
|
|
339
packages/tests/src/api/check-params/user-export.ts
Normal file
339
packages/tests/src/api/check-params/user-export.ts
Normal file
|
@ -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.updateExistingSubConfig({
|
||||
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.updateExistingSubConfig({
|
||||
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
packages/tests/src/api/check-params/user-import.ts
Normal file
169
packages/tests/src/api/check-params/user-import.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import {
|
||||
cleanupTests,
|
||||
createSingleServer, PeerTubeServer,
|
||||
setAccessTokensToServers,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { HttpStatusCode } from '../../../../models/src/http/http-status-codes.js'
|
||||
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 ])
|
||||
})
|
||||
})
|
|
@ -371,7 +371,7 @@ describe('Test video imports API validator', function () {
|
|||
|
||||
async function importVideo () {
|
||||
const attributes = { channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
|
||||
const res = await server.imports.importVideo({ attributes })
|
||||
const res = await server.videoImports.importVideo({ attributes })
|
||||
|
||||
return res.id
|
||||
}
|
||||
|
@ -381,23 +381,23 @@ describe('Test video imports API validator', function () {
|
|||
})
|
||||
|
||||
it('Should fail with an invalid import id', async function () {
|
||||
await server.imports.cancel({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||
await server.imports.delete({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||
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.imports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
await server.imports.delete({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
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.imports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||
await server.imports.delete({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||
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.imports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
await server.imports.delete({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
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 () {
|
||||
|
@ -405,11 +405,11 @@ describe('Test video imports API validator', function () {
|
|||
|
||||
await waitJobs([ server ])
|
||||
|
||||
await server.imports.cancel({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
|
||||
await server.videoImports.cancel({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
|
||||
})
|
||||
|
||||
it('Should succeed to delete an import', async function () {
|
||||
await server.imports.delete({ importId })
|
||||
await server.videoImports.delete({ importId })
|
||||
})
|
||||
|
||||
it('Should fail to delete a pending import', async function () {
|
||||
|
@ -417,13 +417,13 @@ describe('Test video imports API validator', function () {
|
|||
|
||||
importId = await importVideo()
|
||||
|
||||
await server.imports.delete({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
|
||||
await server.videoImports.delete({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
|
||||
})
|
||||
|
||||
it('Should succeed to cancel an import', async function () {
|
||||
importId = await importVideo()
|
||||
|
||||
await server.imports.cancel({ importId })
|
||||
await server.videoImports.cancel({ importId })
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ describe('Test video passwords validator', function () {
|
|||
|
||||
if (mode === 'import') {
|
||||
const attributes = { ...baseCorrectParams, targetUrl: FIXTURE_URLS.goodVideo, videoPasswords }
|
||||
return server.imports.importVideo({ attributes, expectedStatus })
|
||||
return server.videoImports.importVideo({ attributes, expectedStatus })
|
||||
}
|
||||
|
||||
if (mode === 'updateVideo') {
|
||||
|
|
|
@ -8,9 +8,7 @@ import {
|
|||
BlacklistCommand,
|
||||
cleanupTests,
|
||||
createMultipleServers,
|
||||
doubleFollow,
|
||||
killallServers,
|
||||
PeerTubeServer,
|
||||
doubleFollow, PeerTubeServer,
|
||||
setAccessTokensToServers,
|
||||
setDefaultChannelAvatar,
|
||||
waitJobs
|
||||
|
@ -321,18 +319,7 @@ describe('Test video blacklist', function () {
|
|||
before(async function () {
|
||||
this.timeout(20000)
|
||||
|
||||
await killallServers([ servers[0] ])
|
||||
|
||||
const config = {
|
||||
auto_blacklist: {
|
||||
videos: {
|
||||
of_users: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await servers[0].run(config)
|
||||
await servers[0].config.enableAutoBlacklist()
|
||||
|
||||
{
|
||||
const user = { username: 'user_without_flag', password: 'password' }
|
||||
|
@ -380,7 +367,7 @@ describe('Test video blacklist', function () {
|
|||
name: 'URL import',
|
||||
channelId: channelOfUserWithoutFlag
|
||||
}
|
||||
await servers[0].imports.importVideo({ token: userWithoutFlag, attributes })
|
||||
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)
|
||||
|
@ -393,7 +380,7 @@ describe('Test video blacklist', function () {
|
|||
name: 'Torrent import',
|
||||
channelId: channelOfUserWithoutFlag
|
||||
}
|
||||
await servers[0].imports.importVideo({ token: userWithoutFlag, attributes })
|
||||
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)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { wait } from '@peertube/peertube-core-utils'
|
||||
import { AbuseState, CustomConfig, UserNotification, UserRole, VideoPrivacy } from '@peertube/peertube-models'
|
||||
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'
|
||||
|
@ -425,7 +425,6 @@ describe('Test moderation notifications', function () {
|
|||
let uuid: string
|
||||
let shortUUID: string
|
||||
let videoName: string
|
||||
let currentCustomConfig: CustomConfig
|
||||
|
||||
before(async function () {
|
||||
|
||||
|
@ -450,23 +449,7 @@ describe('Test moderation notifications', function () {
|
|||
token: userToken1
|
||||
}
|
||||
|
||||
currentCustomConfig = await servers[0].config.getCustomConfig()
|
||||
|
||||
const autoBlacklistTestsCustomConfig = {
|
||||
...currentCustomConfig,
|
||||
|
||||
autoBlacklist: {
|
||||
videos: {
|
||||
ofUsers: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enable transcoding otherwise own publish notification after transcoding not expected
|
||||
autoBlacklistTestsCustomConfig.transcoding.enabled = true
|
||||
await servers[0].config.updateCustomConfig({ newCustomConfig: autoBlacklistTestsCustomConfig })
|
||||
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 })
|
||||
|
@ -594,8 +577,6 @@ describe('Test moderation notifications', function () {
|
|||
})
|
||||
|
||||
after(async () => {
|
||||
await servers[0].config.updateCustomConfig({ newCustomConfig: currentCustomConfig })
|
||||
|
||||
await servers[0].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
|
||||
await servers[1].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
|
||||
})
|
||||
|
|
|
@ -205,7 +205,7 @@ describe('Test user notifications', function () {
|
|||
privacy: VideoPrivacy.PUBLIC,
|
||||
targetUrl: FIXTURE_URLS.goodVideo
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
@ -349,7 +349,7 @@ describe('Test user notifications', function () {
|
|||
targetUrl: FIXTURE_URLS.goodVideo,
|
||||
waitTranscoding: true
|
||||
}
|
||||
const { video } = await servers[1].imports.importVideo({ attributes })
|
||||
const { video } = await servers[1].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
|
@ -524,7 +524,7 @@ describe('Test user notifications', function () {
|
|||
privacy: VideoPrivacy.PRIVATE,
|
||||
targetUrl: FIXTURE_URLS.badVideo
|
||||
}
|
||||
const { video: { shortUUID } } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: { shortUUID } } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
@ -543,7 +543,7 @@ describe('Test user notifications', function () {
|
|||
privacy: VideoPrivacy.PRIVATE,
|
||||
targetUrl: FIXTURE_URLS.goodVideo
|
||||
}
|
||||
const { video: { shortUUID } } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: { shortUUID } } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ async function importVideo (server: PeerTubeServer) {
|
|||
targetUrl: FIXTURE_URLS.goodVideo720
|
||||
}
|
||||
|
||||
const { video: { uuid } } = await server.imports.importVideo({ attributes })
|
||||
const { video: { uuid } } = await server.videoImports.importVideo({ attributes })
|
||||
|
||||
return uuid
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ describe('Object storage for video import', function () {
|
|||
await setAccessTokensToServers([ server ])
|
||||
await setDefaultVideoChannel([ server ])
|
||||
|
||||
await server.config.enableImports()
|
||||
await server.config.enableVideoImports()
|
||||
})
|
||||
|
||||
describe('Without transcoding', async function () {
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('Test config defaults', function () {
|
|||
|
||||
before(async function () {
|
||||
await server.config.disableTranscoding()
|
||||
await server.config.enableImports()
|
||||
await server.config.enableVideoImports()
|
||||
await server.config.enableLive({ allowReplay: false, transcoding: false })
|
||||
})
|
||||
|
||||
|
@ -82,7 +82,7 @@ describe('Test config defaults', function () {
|
|||
})
|
||||
|
||||
it('Should respect default values when importing a video using URL', async function () {
|
||||
const { video: { id } } = await server.imports.importVideo({
|
||||
const { video: { id } } = await server.videoImports.importVideo({
|
||||
attributes: {
|
||||
...attributes,
|
||||
channelId,
|
||||
|
@ -95,7 +95,7 @@ describe('Test config defaults', function () {
|
|||
})
|
||||
|
||||
it('Should respect default values when importing a video using magnet URI', async function () {
|
||||
const { video: { id } } = await server.imports.importVideo({
|
||||
const { video: { id } } = await server.videoImports.importVideo({
|
||||
attributes: {
|
||||
...attributes,
|
||||
channelId,
|
||||
|
|
|
@ -112,6 +112,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
|||
expect(data.import.videos.concurrency).to.equal(2)
|
||||
expect(data.import.videos.http.enabled).to.be.true
|
||||
expect(data.import.videos.torrent.enabled).to.be.true
|
||||
expect(data.import.videoChannelSynchronization.enabled).to.be.false
|
||||
expect(data.import.users.enabled).to.be.true
|
||||
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false
|
||||
|
||||
expect(data.followers.instance.enabled).to.be.true
|
||||
|
@ -127,6 +129,10 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
|||
expect(data.broadcastMessage.dismissable).to.be.false
|
||||
|
||||
expect(data.storyboards.enabled).to.be.true
|
||||
|
||||
expect(data.export.users.enabled).to.be.true
|
||||
expect(data.export.users.exportExpiration).to.equal(1000 * 3600 * 48)
|
||||
expect(data.export.users.maxUserVideoQuota).to.equal(10737418240)
|
||||
}
|
||||
|
||||
function checkUpdatedConfig (data: CustomConfig) {
|
||||
|
@ -227,6 +233,8 @@ function checkUpdatedConfig (data: CustomConfig) {
|
|||
expect(data.import.videos.concurrency).to.equal(4)
|
||||
expect(data.import.videos.http.enabled).to.be.false
|
||||
expect(data.import.videos.torrent.enabled).to.be.false
|
||||
expect(data.import.videoChannelSynchronization.enabled).to.be.false
|
||||
expect(data.import.users.enabled).to.be.false
|
||||
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true
|
||||
|
||||
expect(data.followers.instance.enabled).to.be.false
|
||||
|
@ -242,6 +250,10 @@ function checkUpdatedConfig (data: CustomConfig) {
|
|||
expect(data.broadcastMessage.dismissable).to.be.true
|
||||
|
||||
expect(data.storyboards.enabled).to.be.false
|
||||
|
||||
expect(data.export.users.enabled).to.be.false
|
||||
expect(data.export.users.exportExpiration).to.equal(43)
|
||||
expect(data.export.users.maxUserVideoQuota).to.equal(42)
|
||||
}
|
||||
|
||||
const newCustomConfig: CustomConfig = {
|
||||
|
@ -415,6 +427,9 @@ const newCustomConfig: CustomConfig = {
|
|||
videoChannelSynchronization: {
|
||||
enabled: false,
|
||||
maxPerUser: 10
|
||||
},
|
||||
users: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
trending: {
|
||||
|
@ -469,6 +484,13 @@ const newCustomConfig: CustomConfig = {
|
|||
},
|
||||
storyboards: {
|
||||
enabled: false
|
||||
},
|
||||
export: {
|
||||
users: {
|
||||
enabled: false,
|
||||
exportExpiration: 43,
|
||||
maxUserVideoQuota: 42
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ describe('Test proxy', function () {
|
|||
describe('Videos import', async function () {
|
||||
|
||||
function quickImport (expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
||||
return servers[0].imports.importVideo({
|
||||
return servers[0].videoImports.importVideo({
|
||||
attributes: {
|
||||
name: 'video import',
|
||||
channelId: servers[0].store.channel.id,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import './oauth.js'
|
||||
import './registrations`.js'
|
||||
import './two-factor.js'
|
||||
import './user-export.js'
|
||||
import './user-import.js'
|
||||
import './user-subscriptions.js'
|
||||
import './user-videos.js'
|
||||
import './users.js'
|
||||
|
|
746
packages/tests/src/api/users/user-export.ts
Normal file
746
packages/tests/src/api/users/user-export.ts
Normal file
|
@ -0,0 +1,746 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
|
||||
import {
|
||||
cleanupTests, getRedirectionUrl, makeActivityPubRawRequest,
|
||||
makeRawRequest,
|
||||
ObjectStorageCommand,
|
||||
PeerTubeServer,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
AccountExportJSON, ActivityPubActor,
|
||||
ActivityPubOrderedCollection,
|
||||
BlocklistExportJSON,
|
||||
ChannelExportJSON,
|
||||
CommentsExportJSON,
|
||||
DislikesExportJSON,
|
||||
FollowersExportJSON,
|
||||
FollowingExportJSON,
|
||||
HttpStatusCode,
|
||||
LikesExportJSON,
|
||||
UserExportState,
|
||||
UserNotificationSettingValue,
|
||||
UserSettingsExportJSON,
|
||||
VideoCommentObject,
|
||||
VideoCreateResult,
|
||||
VideoExportJSON, VideoPlaylistCreateResult,
|
||||
VideoPlaylistPrivacy,
|
||||
VideoPlaylistsExportJSON,
|
||||
VideoPlaylistType,
|
||||
VideoPrivacy
|
||||
} from '@peertube/peertube-models'
|
||||
import {
|
||||
checkExportFileExists,
|
||||
checkFileExistsInZIP,
|
||||
downloadZIP,
|
||||
findVideoObjectInOutbox,
|
||||
parseAPOutbox,
|
||||
parseZIPJSONFile,
|
||||
prepareImportExportTests,
|
||||
regenerateExport
|
||||
} from '@tests/shared/import-export.js'
|
||||
import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
|
||||
import { wait } from '@peertube/peertube-core-utils'
|
||||
|
||||
function runTest (withObjectStorage: boolean) {
|
||||
let server: PeerTubeServer
|
||||
let remoteServer: PeerTubeServer
|
||||
|
||||
let noahToken: string
|
||||
|
||||
let rootId: number
|
||||
let noahId: number
|
||||
let remoteRootId: number
|
||||
|
||||
const emails: object[] = []
|
||||
|
||||
let externalVideo: VideoCreateResult
|
||||
let noahPrivateVideo: VideoCreateResult
|
||||
let noahVideo: VideoCreateResult
|
||||
let mouskaVideo: VideoCreateResult
|
||||
|
||||
let noahPlaylist: VideoPlaylistCreateResult
|
||||
|
||||
let noahExportId: number
|
||||
|
||||
before(async function () {
|
||||
this.timeout(240000)
|
||||
|
||||
const objectStorage = withObjectStorage
|
||||
? new ObjectStorageCommand()
|
||||
: undefined;
|
||||
|
||||
({
|
||||
rootId,
|
||||
noahId,
|
||||
remoteRootId,
|
||||
noahPlaylist,
|
||||
externalVideo,
|
||||
noahPrivateVideo,
|
||||
mouskaVideo,
|
||||
noahVideo,
|
||||
noahToken,
|
||||
server,
|
||||
remoteServer
|
||||
} = await prepareImportExportTests({ emails, objectStorage, withBlockedServer: false }))
|
||||
})
|
||||
|
||||
it('Should export root account', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
{
|
||||
const { data, total } = await server.userExports.list({ userId: rootId })
|
||||
expect(total).to.equal(0)
|
||||
expect(data).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
const beforeRequest = new Date()
|
||||
await server.userExports.request({ userId: rootId, withVideoFiles: false })
|
||||
const afterRequest = new Date()
|
||||
|
||||
{
|
||||
const { data, total } = await server.userExports.list({ userId: rootId })
|
||||
expect(total).to.equal(1)
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
||||
expect(data[0].id).to.exist
|
||||
expect(new Date(data[0].createdAt)).to.be.greaterThan(beforeRequest)
|
||||
expect(new Date(data[0].createdAt)).to.be.below(afterRequest)
|
||||
|
||||
await server.userExports.waitForCreation({ userId: rootId })
|
||||
}
|
||||
|
||||
{
|
||||
const { data, total } = await server.userExports.list({ userId: rootId })
|
||||
expect(total).to.equal(1)
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
||||
expect(data[0].privateDownloadUrl).to.exist
|
||||
expect(data[0].size).to.be.greaterThan(0)
|
||||
expect(data[0].state.id).to.equal(UserExportState.COMPLETED)
|
||||
expect(data[0].state.label).to.equal('Completed')
|
||||
}
|
||||
|
||||
await waitJobs([ server ])
|
||||
})
|
||||
|
||||
it('Should have received an email on archive creation', async function () {
|
||||
const email = emails.find(e => {
|
||||
return e['to'][0]['address'] === 'admin' + server.internalServerNumber + '@example.com' &&
|
||||
e['subject'].includes('export archive has been created')
|
||||
})
|
||||
|
||||
expect(email).to.exist
|
||||
|
||||
expect(email['text']).to.contain('has been created')
|
||||
expect(email['text']).to.contain(server.url + '/my-account/import-export')
|
||||
})
|
||||
|
||||
it('Should have a valid ZIP for root account', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const zip = await downloadZIP(server, rootId)
|
||||
|
||||
const files = [
|
||||
'activity-pub/actor.json',
|
||||
'activity-pub/dislikes.json',
|
||||
'activity-pub/following.json',
|
||||
'activity-pub/likes.json',
|
||||
'activity-pub/outbox.json',
|
||||
|
||||
'peertube/account.json',
|
||||
'peertube/blocklist.json',
|
||||
'peertube/channels.json',
|
||||
'peertube/comments.json',
|
||||
'peertube/dislikes.json',
|
||||
'peertube/follower.json',
|
||||
'peertube/following.json',
|
||||
'peertube/likes.json',
|
||||
'peertube/user-settings.json',
|
||||
'peertube/video-playlists.json',
|
||||
'peertube/videos.json'
|
||||
]
|
||||
|
||||
for (const file of files) {
|
||||
expect(zip.files[file]).to.exist
|
||||
|
||||
const string = await zip.file(file).async('string')
|
||||
expect(string).to.have.length.greaterThan(0)
|
||||
|
||||
expect(JSON.parse(string)).to.not.throw
|
||||
}
|
||||
|
||||
const filepaths = Object.keys(zip.files)
|
||||
const staticFilepaths = filepaths.filter(p => p.startsWith('files/'))
|
||||
expect(staticFilepaths).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should export Noah account', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
await server.userExports.request({ userId: noahId, withVideoFiles: true })
|
||||
await server.userExports.waitForCreation({ userId: noahId })
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
|
||||
for (const file of Object.keys(zip.files)) {
|
||||
await checkFileExistsInZIP(zip, file)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have a valid ActivityPub export', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
|
||||
{
|
||||
const actor = await parseZIPJSONFile<ActivityPubActor>(zip, 'activity-pub/actor.json')
|
||||
|
||||
expect(actor['@context']).to.exist
|
||||
expect(actor.type).to.equal('Person')
|
||||
expect(actor.id).to.equal(server.url + '/accounts/noah')
|
||||
expect(actor.following).to.equal('following.json')
|
||||
expect(actor.outbox).to.equal('outbox.json')
|
||||
expect(actor.preferredUsername).to.equal('noah')
|
||||
expect(actor.publicKey).to.exist
|
||||
|
||||
expect(actor.icon).to.have.lengthOf(0)
|
||||
|
||||
expect(actor.likes).to.equal('likes.json')
|
||||
expect(actor.dislikes).to.equal('dislikes.json')
|
||||
}
|
||||
|
||||
{
|
||||
const dislikes = await parseZIPJSONFile<ActivityPubOrderedCollection<string>>(zip, 'activity-pub/dislikes.json')
|
||||
expect(dislikes['@context']).to.exist
|
||||
expect(dislikes.id).to.equal('dislikes.json')
|
||||
expect(dislikes.type).to.equal('OrderedCollection')
|
||||
expect(dislikes.totalItems).to.equal(1)
|
||||
expect(dislikes.orderedItems).to.have.lengthOf(1)
|
||||
expect(dislikes.orderedItems[0]).to.equal(remoteServer.url + '/videos/watch/' + externalVideo.uuid)
|
||||
}
|
||||
|
||||
{
|
||||
const likes = await parseZIPJSONFile<ActivityPubOrderedCollection<string>>(zip, 'activity-pub/likes.json')
|
||||
expect(likes['@context']).to.exist
|
||||
expect(likes.id).to.equal('likes.json')
|
||||
expect(likes.type).to.equal('OrderedCollection')
|
||||
expect(likes.totalItems).to.equal(2)
|
||||
expect(likes.orderedItems).to.have.lengthOf(2)
|
||||
expect(likes.orderedItems.find(i => i === server.url + '/videos/watch/' + noahVideo.uuid)).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const following = await parseZIPJSONFile<ActivityPubOrderedCollection<string>>(zip, 'activity-pub/following.json')
|
||||
expect(following['@context']).to.exist
|
||||
expect(following.id).to.equal('following.json')
|
||||
expect(following.type).to.equal('OrderedCollection')
|
||||
expect(following.totalItems).to.equal(2)
|
||||
expect(following.orderedItems).to.have.lengthOf(2)
|
||||
expect(following.orderedItems.find(i => i === remoteServer.url + '/video-channels/root_channel')).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const outbox = await parseAPOutbox(zip)
|
||||
expect(outbox['@context']).to.exist
|
||||
expect(outbox.id).to.equal('outbox.json')
|
||||
expect(outbox.type).to.equal('OrderedCollection')
|
||||
|
||||
// 3 videos and 2 comments
|
||||
expect(outbox.totalItems).to.equal(5)
|
||||
expect(outbox.orderedItems).to.have.lengthOf(5)
|
||||
|
||||
expect(outbox.orderedItems.filter(i => i.object.type === 'Video')).to.have.lengthOf(3)
|
||||
expect(outbox.orderedItems.filter(i => i.object.type === 'Note')).to.have.lengthOf(2)
|
||||
|
||||
const { object: video } = findVideoObjectInOutbox(outbox, 'noah public video')
|
||||
|
||||
// Thumbnail
|
||||
expect(video.icon).to.have.lengthOf(1)
|
||||
expect(video.icon[0].url).to.equal('../files/videos/thumbnails/' + noahVideo.uuid + '.jpg')
|
||||
|
||||
await checkFileExistsInZIP(zip, video.icon[0].url, '/activity-pub')
|
||||
|
||||
// Subtitles
|
||||
expect(video.subtitleLanguage).to.have.lengthOf(2)
|
||||
for (const subtitle of video.subtitleLanguage) {
|
||||
await checkFileExistsInZIP(zip, subtitle.url, '/activity-pub')
|
||||
}
|
||||
|
||||
expect(video.attachment).to.have.lengthOf(1)
|
||||
expect(video.attachment[0].url).to.equal('../files/videos/video-files/' + noahVideo.uuid + '.webm')
|
||||
await checkFileExistsInZIP(zip, video.attachment[0].url, '/activity-pub')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have a valid export in PeerTube format', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<BlocklistExportJSON>(zip, 'peertube/blocklist.json')
|
||||
|
||||
expect(json.instances).to.have.lengthOf(0)
|
||||
expect(json.actors).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<FollowersExportJSON>(zip, 'peertube/follower.json')
|
||||
expect(json.followers).to.have.lengthOf(2)
|
||||
|
||||
const follower = json.followers.find(f => {
|
||||
return f.handle === 'root@' + remoteServer.host
|
||||
})
|
||||
|
||||
expect(follower).to.exist
|
||||
expect(follower.targetHandle).to.equal('noah_channel@' + server.host)
|
||||
expect(follower.createdAt).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<FollowingExportJSON>(zip, 'peertube/following.json')
|
||||
expect(json.following).to.have.lengthOf(2)
|
||||
|
||||
const following = json.following.find(f => {
|
||||
return f.targetHandle === 'mouska_channel@' + server.host
|
||||
})
|
||||
|
||||
expect(following).to.exist
|
||||
expect(following.handle).to.equal('noah@' + server.host)
|
||||
expect(following.createdAt).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<LikesExportJSON>(zip, 'peertube/likes.json')
|
||||
expect(json.likes).to.have.lengthOf(2)
|
||||
|
||||
const like = json.likes.find(l => {
|
||||
return l.videoUrl === server.url + '/videos/watch/' + mouskaVideo.uuid
|
||||
})
|
||||
expect(like).to.exist
|
||||
expect(like.createdAt).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<DislikesExportJSON>(zip, 'peertube/dislikes.json')
|
||||
expect(json.dislikes).to.have.lengthOf(1)
|
||||
|
||||
const dislike = json.dislikes.find(l => {
|
||||
return l.videoUrl === remoteServer.url + '/videos/watch/' + externalVideo.uuid
|
||||
})
|
||||
expect(dislike).to.exist
|
||||
expect(dislike.createdAt).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<UserSettingsExportJSON>(zip, 'peertube/user-settings.json')
|
||||
expect(json.email).to.equal('noah@example.com')
|
||||
expect(json.p2pEnabled).to.be.false
|
||||
expect(json.notificationSettings.myVideoPublished).to.equal(UserNotificationSettingValue.NONE)
|
||||
expect(json.notificationSettings.commentMention).to.equal(UserNotificationSettingValue.EMAIL)
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<AccountExportJSON>(zip, 'peertube/account.json')
|
||||
expect(json.displayName).to.equal('noah')
|
||||
expect(json.description).to.equal('super noah description')
|
||||
expect(json.name).to.equal('noah')
|
||||
expect(json.avatars).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<VideoPlaylistsExportJSON>(zip, 'peertube/video-playlists.json')
|
||||
|
||||
expect(json.videoPlaylists).to.have.lengthOf(3)
|
||||
|
||||
// Watch later
|
||||
{
|
||||
expect(json.videoPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const playlist1 = json.videoPlaylists.find(p => p.displayName === 'noah playlist 1')
|
||||
expect(playlist1.privacy).to.equal(VideoPlaylistPrivacy.PUBLIC)
|
||||
expect(playlist1.channel.name).to.equal('noah_channel')
|
||||
expect(playlist1.elements).to.have.lengthOf(3)
|
||||
expect(playlist1.type).to.equal(VideoPlaylistType.REGULAR)
|
||||
|
||||
await makeRawRequest({ url: playlist1.thumbnailUrl, expectedStatus: HttpStatusCode.OK_200 })
|
||||
|
||||
expect(playlist1.elements.find(e => e.videoUrl === server.url + '/videos/watch/' + mouskaVideo.uuid)).to.exist
|
||||
expect(playlist1.elements.find(e => e.videoUrl === server.url + '/videos/watch/' + noahPrivateVideo.uuid)).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const playlist2 = json.videoPlaylists.find(p => p.displayName === 'noah playlist 2')
|
||||
expect(playlist2.privacy).to.equal(VideoPlaylistPrivacy.PRIVATE)
|
||||
expect(playlist2.channel.name).to.not.exist
|
||||
expect(playlist2.elements).to.have.lengthOf(0)
|
||||
expect(playlist2.type).to.equal(VideoPlaylistType.REGULAR)
|
||||
expect(playlist2.thumbnailUrl).to.not.exist
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<ChannelExportJSON>(zip, 'peertube/channels.json')
|
||||
|
||||
expect(json.channels).to.have.lengthOf(2)
|
||||
|
||||
{
|
||||
const mainChannel = json.channels.find(c => c.name === 'noah_channel')
|
||||
expect(mainChannel.displayName).to.equal('Main noah channel')
|
||||
expect(mainChannel.avatars).to.have.lengthOf(0)
|
||||
expect(mainChannel.banners).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const secondaryChannel = json.channels.find(c => c.name === 'noah_second_channel')
|
||||
expect(secondaryChannel.displayName).to.equal('noah display name')
|
||||
expect(secondaryChannel.description).to.equal('noah description')
|
||||
expect(secondaryChannel.support).to.equal('noah support')
|
||||
|
||||
expect(secondaryChannel.avatars).to.have.lengthOf(2)
|
||||
expect(secondaryChannel.banners).to.have.lengthOf(1)
|
||||
|
||||
const urls = [ ...secondaryChannel.avatars, ...secondaryChannel.banners ].map(a => a.url)
|
||||
for (const url of urls) {
|
||||
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<CommentsExportJSON>(zip, 'peertube/comments.json')
|
||||
|
||||
expect(json.comments).to.have.lengthOf(2)
|
||||
|
||||
{
|
||||
const thread = json.comments.find(c => c.text === 'noah comment')
|
||||
|
||||
expect(thread.videoUrl).to.equal(server.url + '/videos/watch/' + mouskaVideo.uuid)
|
||||
expect(thread.inReplyToCommentUrl).to.not.exist
|
||||
}
|
||||
|
||||
{
|
||||
const reply = json.comments.find(c => c.text === 'noah reply')
|
||||
|
||||
expect(reply.videoUrl).to.equal(server.url + '/videos/watch/' + noahVideo.uuid)
|
||||
expect(reply.inReplyToCommentUrl).to.exist
|
||||
|
||||
const { body } = await makeActivityPubRawRequest(reply.inReplyToCommentUrl)
|
||||
expect((body as VideoCommentObject).content).to.equal('local comment')
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<VideoExportJSON>(zip, 'peertube/videos.json')
|
||||
|
||||
expect(json.videos).to.have.lengthOf(3)
|
||||
|
||||
{
|
||||
const privateVideo = json.videos.find(v => v.name === 'noah private video')
|
||||
expect(privateVideo).to.exist
|
||||
|
||||
expect(privateVideo.channel.name).to.equal('noah_channel')
|
||||
expect(privateVideo.privacy).to.equal(VideoPrivacy.PRIVATE)
|
||||
|
||||
expect(privateVideo.captions).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const publicVideo = json.videos.find(v => v.name === 'noah public video')
|
||||
expect(publicVideo).to.exist
|
||||
|
||||
expect(publicVideo.channel.name).to.equal('noah_channel')
|
||||
expect(publicVideo.privacy).to.equal(VideoPrivacy.PUBLIC)
|
||||
|
||||
expect(publicVideo.files).to.have.lengthOf(1)
|
||||
expect(publicVideo.streamingPlaylists).to.have.lengthOf(0)
|
||||
|
||||
expect(publicVideo.captions).to.have.lengthOf(2)
|
||||
|
||||
expect(publicVideo.captions.find(c => c.language === 'ar')).to.exist
|
||||
expect(publicVideo.captions.find(c => c.language === 'fr')).to.exist
|
||||
|
||||
const urls = [
|
||||
...publicVideo.captions.map(c => c.fileUrl),
|
||||
...publicVideo.files.map(f => f.fileUrl),
|
||||
publicVideo.thumbnailUrl
|
||||
]
|
||||
|
||||
for (const url of urls) {
|
||||
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const secondaryChannelVideo = json.videos.find(v => v.name === 'noah public video second channel')
|
||||
expect(secondaryChannelVideo.channel.name).to.equal('noah_second_channel')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have a valid export of static files', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
const files = Object.keys(zip.files)
|
||||
|
||||
{
|
||||
expect(zip.files['files/account/avatars/noah.jpg']).to.not.exist
|
||||
}
|
||||
|
||||
{
|
||||
const playlistFiles = files.filter(f => f.startsWith('files/video-playlists/thumbnails/'))
|
||||
expect(playlistFiles).to.have.lengthOf(1)
|
||||
|
||||
await checkFileExistsInZIP(zip, 'files/video-playlists/thumbnails/' + noahPlaylist.uuid + '.jpg')
|
||||
}
|
||||
|
||||
{
|
||||
const channelAvatarFiles = files.filter(f => f.startsWith('files/channels/avatars/'))
|
||||
expect(channelAvatarFiles).to.have.lengthOf(1)
|
||||
|
||||
const channelBannerFiles = files.filter(f => f.startsWith('files/channels/banners/'))
|
||||
expect(channelBannerFiles).to.have.lengthOf(1)
|
||||
|
||||
await checkFileExistsInZIP(zip, 'files/channels/avatars/noah_second_channel.png')
|
||||
await checkFileExistsInZIP(zip, 'files/channels/banners/noah_second_channel.jpg')
|
||||
}
|
||||
|
||||
{
|
||||
const videoThumbnails = files.filter(f => f.startsWith('files/videos/thumbnails/'))
|
||||
expect(videoThumbnails).to.have.lengthOf(3)
|
||||
|
||||
const videoFiles = files.filter(f => f.startsWith('files/videos/video-files/'))
|
||||
expect(videoFiles).to.have.lengthOf(3)
|
||||
|
||||
await checkFileExistsInZIP(zip, 'files/videos/thumbnails/' + noahPrivateVideo.uuid + '.jpg')
|
||||
await checkFileExistsInZIP(zip, 'files/videos/video-files/' + noahPrivateVideo.uuid + '.webm')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not export Noah videos', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
await regenerateExport({ server, userId: noahId, withVideoFiles: false })
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
|
||||
{
|
||||
const outbox = await parseAPOutbox(zip)
|
||||
const { object: video } = findVideoObjectInOutbox(outbox, 'noah public video')
|
||||
|
||||
expect(video.attachment).to.not.exist
|
||||
}
|
||||
|
||||
{
|
||||
const files = Object.keys(zip.files)
|
||||
|
||||
const videoFiles = files.filter(f => f.startsWith('files/videos/video-files/'))
|
||||
expect(videoFiles).to.have.lengthOf(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should update my avatar and include it in the archive', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
await server.users.updateMyAvatar({ token: noahToken, fixture: 'avatar.png' })
|
||||
|
||||
await regenerateExport({ server, userId: noahId, withVideoFiles: false })
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
|
||||
// AP
|
||||
{
|
||||
const actor = await parseZIPJSONFile<ActivityPubActor>(zip, 'activity-pub/actor.json')
|
||||
|
||||
expect(actor.icon).to.have.lengthOf(1)
|
||||
|
||||
await checkFileExistsInZIP(zip, actor.icon[0].url, '/activity-pub')
|
||||
}
|
||||
|
||||
// PeerTube format
|
||||
{
|
||||
const json = await parseZIPJSONFile<AccountExportJSON>(zip, 'peertube/account.json')
|
||||
expect(json.avatars).to.have.lengthOf(2)
|
||||
|
||||
for (const avatar of json.avatars) {
|
||||
await makeRawRequest({ url: avatar.url, expectedStatus: HttpStatusCode.OK_200 })
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await checkFileExistsInZIP(zip, 'files/account/avatars/noah.png')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should add account and server in blocklist and include it in the archive', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
const blocks = [
|
||||
{ account: 'root' },
|
||||
{ account: 'root@' + remoteServer.host },
|
||||
{ server: remoteServer.host }
|
||||
]
|
||||
|
||||
for (const toBlock of blocks) {
|
||||
await server.blocklist.addToMyBlocklist({ token: noahToken, ...toBlock })
|
||||
}
|
||||
|
||||
const { export: { id } } = await regenerateExport({ server, userId: noahId, withVideoFiles: false })
|
||||
noahExportId = id
|
||||
|
||||
const zip = await downloadZIP(server, noahId)
|
||||
const json = await parseZIPJSONFile<BlocklistExportJSON>(zip, 'peertube/blocklist.json')
|
||||
|
||||
expect(json.instances).to.have.lengthOf(1)
|
||||
expect(json.instances[0].host).to.equal(remoteServer.host)
|
||||
|
||||
expect(json.actors).to.have.lengthOf(2)
|
||||
expect(json.actors.find(a => a.handle === 'root@' + server.host)).to.exist
|
||||
expect(json.actors.find(a => a.handle === 'root@' + remoteServer.host)).to.exist
|
||||
|
||||
for (const toBlock of blocks) {
|
||||
await server.blocklist.removeFromMyBlocklist({ token: noahToken, ...toBlock })
|
||||
}
|
||||
})
|
||||
|
||||
it('Should export videos on instance with transcoding enabled', async function () {
|
||||
await regenerateExport({ server: remoteServer, userId: remoteRootId, withVideoFiles: true })
|
||||
|
||||
const zip = await downloadZIP(remoteServer, remoteRootId)
|
||||
|
||||
{
|
||||
const json = await parseZIPJSONFile<VideoExportJSON>(zip, 'peertube/videos.json')
|
||||
|
||||
expect(json.videos).to.have.lengthOf(1)
|
||||
const video = json.videos[0]
|
||||
|
||||
expect(video.files).to.have.lengthOf(4)
|
||||
expect(video.streamingPlaylists).to.have.lengthOf(1)
|
||||
expect(video.streamingPlaylists[0].files).to.have.lengthOf(4)
|
||||
}
|
||||
|
||||
{
|
||||
const outbox = await parseAPOutbox(zip)
|
||||
const { object: video } = findVideoObjectInOutbox(outbox, 'external video')
|
||||
|
||||
expect(video.attachment).to.have.lengthOf(1)
|
||||
expect(video.attachment[0].url).to.equal('../files/videos/video-files/' + externalVideo.uuid + '.mp4')
|
||||
await checkFileExistsInZIP(zip, video.attachment[0].url, '/activity-pub')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should delete the export and clean up the disk', async function () {
|
||||
const { data, total } = await server.userExports.list({ userId: noahId })
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(total).to.equal(1)
|
||||
|
||||
const userExport = data[0]
|
||||
const redirectedUrl = withObjectStorage
|
||||
? await getRedirectionUrl(userExport.privateDownloadUrl)
|
||||
: undefined
|
||||
|
||||
await checkExportFileExists({ exists: true, server, userExport, redirectedUrl, withObjectStorage })
|
||||
|
||||
await server.userExports.delete({ userId: noahId, exportId: noahExportId, token: noahToken })
|
||||
|
||||
{
|
||||
const { data, total } = await server.userExports.list({ userId: noahId })
|
||||
expect(data).to.have.lengthOf(0)
|
||||
expect(total).to.equal(0)
|
||||
|
||||
await checkExportFileExists({ exists: false, server, userExport, redirectedUrl, withObjectStorage })
|
||||
}
|
||||
})
|
||||
|
||||
it('Should remove the user and cleanup the disk', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
const { token, userId } = await server.users.generate('to_delete')
|
||||
await server.userExports.request({ userId, token, withVideoFiles: false })
|
||||
await server.userExports.waitForCreation({ userId, token })
|
||||
|
||||
const { data } = await server.userExports.list({ userId })
|
||||
|
||||
const userExport = data[0]
|
||||
const redirectedUrl = withObjectStorage
|
||||
? await getRedirectionUrl(userExport.privateDownloadUrl)
|
||||
: undefined
|
||||
|
||||
await checkExportFileExists({ exists: true, server, userExport, redirectedUrl, withObjectStorage })
|
||||
|
||||
await server.users.remove({ userId })
|
||||
|
||||
await checkExportFileExists({ exists: false, server, userExport, redirectedUrl, withObjectStorage })
|
||||
})
|
||||
|
||||
it('Should expire old archives', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
await server.userExports.request({ userId: noahId, withVideoFiles: true })
|
||||
await server.userExports.waitForCreation({ userId: noahId })
|
||||
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
|
||||
const { data } = await server.userExports.list({ userId: noahId })
|
||||
expect(new Date(data[0].expiresOn)).to.be.greaterThan(tomorrow)
|
||||
|
||||
const userExport = data[0]
|
||||
const redirectedUrl = withObjectStorage
|
||||
? await getRedirectionUrl(userExport.privateDownloadUrl)
|
||||
: undefined
|
||||
|
||||
await checkExportFileExists({ exists: true, server, userExport, withObjectStorage, redirectedUrl })
|
||||
|
||||
await server.config.updateCustomSubConfig({
|
||||
newConfig: {
|
||||
export: {
|
||||
users: {
|
||||
exportExpiration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await server.debug.sendCommand({
|
||||
body: {
|
||||
command: 'remove-expired-user-exports'
|
||||
}
|
||||
})
|
||||
|
||||
// File deletion
|
||||
await wait(500)
|
||||
|
||||
{
|
||||
const { data } = await server.userExports.list({ userId: noahId })
|
||||
expect(data).to.have.lengthOf(0)
|
||||
|
||||
await checkExportFileExists({ exists: false, server, userExport, withObjectStorage, redirectedUrl })
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
MockSmtpServer.Instance.kill()
|
||||
|
||||
await cleanupTests([ server, remoteServer ])
|
||||
})
|
||||
}
|
||||
|
||||
describe('Test user export', function () {
|
||||
|
||||
describe('From filesystem', function () {
|
||||
runTest(false)
|
||||
})
|
||||
|
||||
describe('From object storage', function () {
|
||||
if (areMockObjectStorageTestsDisabled()) return
|
||||
|
||||
runTest(true)
|
||||
})
|
||||
})
|
556
packages/tests/src/api/users/user-import.ts
Normal file
556
packages/tests/src/api/users/user-import.ts
Normal file
|
@ -0,0 +1,556 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
|
||||
import {
|
||||
cleanupTests, makeRawRequest,
|
||||
ObjectStorageCommand,
|
||||
PeerTubeServer, waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
UserImportState,
|
||||
UserNotificationSettingValue,
|
||||
VideoCreateResult,
|
||||
VideoPlaylistPrivacy,
|
||||
VideoPlaylistType,
|
||||
VideoPrivacy
|
||||
} from '@peertube/peertube-models'
|
||||
import { prepareImportExportTests } from '@tests/shared/import-export.js'
|
||||
import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
|
||||
import { writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { expect } from 'chai'
|
||||
import { testImage, testImageSize } from '@tests/shared/checks.js'
|
||||
import { completeVideoCheck } from '@tests/shared/videos.js'
|
||||
import { completeCheckHlsPlaylist } from '@tests/shared/streaming-playlists.js'
|
||||
|
||||
function runTest (withObjectStorage: boolean) {
|
||||
let server: PeerTubeServer
|
||||
let remoteServer: PeerTubeServer
|
||||
let blockedServer: PeerTubeServer
|
||||
|
||||
let noahToken: string
|
||||
|
||||
let noahId: number
|
||||
|
||||
const emails: object[] = []
|
||||
|
||||
let externalVideo: VideoCreateResult
|
||||
let noahVideo: VideoCreateResult
|
||||
let mouskaVideo: VideoCreateResult
|
||||
|
||||
let remoteNoahToken: string
|
||||
let remoteNoahId: number
|
||||
|
||||
let archivePath: string
|
||||
|
||||
let objectStorage: ObjectStorageCommand
|
||||
|
||||
let latestImportId: number
|
||||
|
||||
before(async function () {
|
||||
this.timeout(240000)
|
||||
|
||||
objectStorage = withObjectStorage
|
||||
? new ObjectStorageCommand()
|
||||
: undefined;
|
||||
|
||||
({
|
||||
noahId,
|
||||
externalVideo,
|
||||
noahVideo,
|
||||
noahToken,
|
||||
server,
|
||||
remoteNoahId,
|
||||
remoteNoahToken,
|
||||
remoteServer,
|
||||
mouskaVideo,
|
||||
blockedServer
|
||||
} = await prepareImportExportTests({ emails, objectStorage, withBlockedServer: true }))
|
||||
|
||||
await blockedServer.videos.quickUpload({ name: 'blocked video' })
|
||||
await waitJobs([ blockedServer ])
|
||||
|
||||
// Also add some blocks
|
||||
const blocks = [
|
||||
{ account: 'mouska' },
|
||||
{ account: 'root@' + blockedServer.host },
|
||||
{ server: blockedServer.host }
|
||||
]
|
||||
|
||||
for (const toBlock of blocks) {
|
||||
await server.blocklist.addToMyBlocklist({ token: noahToken, ...toBlock })
|
||||
}
|
||||
|
||||
// Add avatars
|
||||
await server.users.updateMyAvatar({ token: noahToken, fixture: 'avatar.gif' })
|
||||
|
||||
// Add password protected video
|
||||
await server.videos.upload({
|
||||
token: noahToken,
|
||||
attributes: {
|
||||
name: 'noah password video',
|
||||
privacy: VideoPrivacy.PASSWORD_PROTECTED,
|
||||
videoPasswords: [ 'password1', 'password2' ]
|
||||
}
|
||||
})
|
||||
|
||||
// Add a video in watch later playlist
|
||||
const { data: playlists } = await server.playlists.listByAccount({
|
||||
token: noahToken,
|
||||
handle: 'noah',
|
||||
playlistType: VideoPlaylistType.WATCH_LATER
|
||||
})
|
||||
|
||||
await server.playlists.addElement({
|
||||
playlistId: playlists[0].id,
|
||||
attributes: { videoId: noahVideo.uuid }
|
||||
})
|
||||
|
||||
await waitJobs([ server, remoteServer, blockedServer ])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
await server.userExports.request({ userId: noahId, withVideoFiles: true })
|
||||
await server.userExports.waitForCreation({ userId: noahId })
|
||||
|
||||
const { data } = await server.userExports.list({ userId: noahId })
|
||||
|
||||
const res = await makeRawRequest({
|
||||
url: data[0].privateDownloadUrl,
|
||||
responseType: 'arraybuffer',
|
||||
redirects: 1,
|
||||
expectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
|
||||
archivePath = join(server.getDirectoryPath('tmp'), 'archive.zip')
|
||||
await writeFile(archivePath, res.body)
|
||||
})
|
||||
|
||||
it('Should import an archive with video files', async function () {
|
||||
this.timeout(240000)
|
||||
|
||||
const { userImport } = await remoteServer.userImports.importArchive({ fixture: archivePath, userId: remoteNoahId })
|
||||
latestImportId = userImport.id
|
||||
|
||||
await waitJobs([ server, remoteServer ])
|
||||
})
|
||||
|
||||
it('Should have a valid import status', async function () {
|
||||
const userImport = await remoteServer.userImports.getLatestImport({ userId: remoteNoahId, token: remoteNoahToken })
|
||||
|
||||
expect(userImport.id).to.equal(latestImportId)
|
||||
expect(userImport.state.id).to.equal(UserImportState.COMPLETED)
|
||||
expect(userImport.state.label).to.equal('Completed')
|
||||
})
|
||||
|
||||
it('Should have correctly imported blocklist', async function () {
|
||||
{
|
||||
const { data } = await remoteServer.blocklist.listMyAccountBlocklist({ start: 0, count: 5, token: remoteNoahToken })
|
||||
|
||||
expect(data).to.have.lengthOf(2)
|
||||
expect(data.find(a => a.blockedAccount.host === server.host && a.blockedAccount.name === 'mouska')).to.exist
|
||||
expect(data.find(a => a.blockedAccount.host === blockedServer.host && a.blockedAccount.name === 'root')).to.exist
|
||||
}
|
||||
|
||||
{
|
||||
const { data } = await remoteServer.blocklist.listMyServerBlocklist({ start: 0, count: 5, token: remoteNoahToken })
|
||||
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data.find(a => a.blockedServer.host === blockedServer.host)).to.exist
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported account', async function () {
|
||||
const me = await remoteServer.users.getMyInfo({ token: remoteNoahToken })
|
||||
|
||||
expect(me.account.displayName).to.equal('noah')
|
||||
expect(me.username).to.equal('noah_remote')
|
||||
expect(me.account.description).to.equal('super noah description')
|
||||
|
||||
for (const avatar of me.account.avatars) {
|
||||
await testImageSize(remoteServer.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.gif')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported user settings', async function () {
|
||||
{
|
||||
const me = await remoteServer.users.getMyInfo({ token: remoteNoahToken })
|
||||
|
||||
expect(me.p2pEnabled).to.be.false
|
||||
|
||||
const settings = me.notificationSettings
|
||||
|
||||
expect(settings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL)
|
||||
expect(settings.myVideoPublished).to.equal(UserNotificationSettingValue.NONE)
|
||||
expect(settings.commentMention).to.equal(UserNotificationSettingValue.EMAIL)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported channels', async function () {
|
||||
const { data: channels } = await remoteServer.channels.listByAccount({ token: remoteNoahToken, accountName: 'noah_remote' })
|
||||
|
||||
// One default + 2 imported
|
||||
expect(channels).to.have.lengthOf(3)
|
||||
|
||||
await remoteServer.channels.get({ token: remoteNoahToken, channelName: 'noah_remote_channel' })
|
||||
|
||||
const importedMain = await remoteServer.channels.get({ token: remoteNoahToken, channelName: 'noah_channel' })
|
||||
expect(importedMain.displayName).to.equal('Main noah channel')
|
||||
expect(importedMain.avatars).to.have.lengthOf(0)
|
||||
expect(importedMain.banners).to.have.lengthOf(0)
|
||||
|
||||
const importedSecond = await remoteServer.channels.get({ token: remoteNoahToken, channelName: 'noah_second_channel' })
|
||||
expect(importedSecond.displayName).to.equal('noah display name')
|
||||
expect(importedSecond.description).to.equal('noah description')
|
||||
expect(importedSecond.support).to.equal('noah support')
|
||||
|
||||
await testImage(remoteServer.url, 'banner-resized', importedSecond.banners[0].path)
|
||||
|
||||
for (const avatar of importedSecond.avatars) {
|
||||
await testImage(remoteServer.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.png')
|
||||
}
|
||||
|
||||
{
|
||||
// Also check the correct count on origin server
|
||||
const { data: channels } = await server.channels.listByAccount({ accountName: 'noah_remote@' + remoteServer.host })
|
||||
expect(channels).to.have.lengthOf(2) // noah_remote_channel doesn't have videos so it has not been federated
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported following', async function () {
|
||||
const { data } = await remoteServer.subscriptions.list({ token: remoteNoahToken })
|
||||
|
||||
expect(data).to.have.lengthOf(2)
|
||||
expect(data.find(f => f.name === 'mouska_channel' && f.host === server.host)).to.exist
|
||||
expect(data.find(f => f.name === 'root_channel' && f.host === remoteServer.host)).to.exist
|
||||
})
|
||||
|
||||
it('Should not have reimported followers (it is not a migration)', async function () {
|
||||
for (const checkServer of [ server, remoteServer ]) {
|
||||
const { data } = await checkServer.channels.listFollowers({ channelName: 'noah_channel@' + remoteServer.host })
|
||||
|
||||
expect(data).to.have.lengthOf(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not have imported comments (it is not a migration)', async function () {
|
||||
for (const checkServer of [ server, remoteServer ]) {
|
||||
{
|
||||
const threads = await checkServer.comments.listThreads({ videoId: noahVideo.uuid })
|
||||
expect(threads.total).to.equal(2)
|
||||
}
|
||||
|
||||
{
|
||||
const threads = await checkServer.comments.listThreads({ videoId: mouskaVideo.uuid })
|
||||
expect(threads.total).to.equal(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported likes/dislikes', async function () {
|
||||
{
|
||||
const { rating } = await remoteServer.users.getMyRating({ videoId: mouskaVideo.uuid, token: remoteNoahToken })
|
||||
expect(rating).to.equal('like')
|
||||
|
||||
for (const checkServer of [ server, remoteServer ]) {
|
||||
const video = await checkServer.videos.get({ id: mouskaVideo.uuid })
|
||||
expect(video.likes).to.equal(2) // Old account + new account rates
|
||||
expect(video.dislikes).to.equal(0)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const { rating } = await remoteServer.users.getMyRating({ videoId: noahVideo.uuid, token: remoteNoahToken })
|
||||
expect(rating).to.equal('like')
|
||||
}
|
||||
|
||||
{
|
||||
const { rating } = await remoteServer.users.getMyRating({ videoId: externalVideo.uuid, token: remoteNoahToken })
|
||||
expect(rating).to.equal('dislike')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported user video playlists', async function () {
|
||||
const { data } = await remoteServer.playlists.listByAccount({ handle: 'noah_remote', token: remoteNoahToken })
|
||||
|
||||
// Should merge the watch later playlists
|
||||
expect(data).to.have.lengthOf(3)
|
||||
|
||||
{
|
||||
const watchLater = data.find(p => p.type.id === VideoPlaylistType.WATCH_LATER)
|
||||
expect(watchLater).to.exist
|
||||
expect(watchLater.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
|
||||
|
||||
// Playlists were merged
|
||||
expect(watchLater.videosLength).to.equal(1)
|
||||
|
||||
const { data: videos } = await remoteServer.playlists.listVideos({ playlistId: watchLater.id, token: remoteNoahToken })
|
||||
expect(videos[0].position).to.equal(1)
|
||||
expect(videos[0].video.uuid).to.equal(noahVideo.uuid)
|
||||
|
||||
// Not federated
|
||||
await server.playlists.get({ playlistId: watchLater.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
}
|
||||
|
||||
{
|
||||
const playlist1 = data.find(p => p.displayName === 'noah playlist 1')
|
||||
expect(playlist1).to.exist
|
||||
|
||||
expect(playlist1.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
|
||||
expect(playlist1.videosLength).to.equal(2) // 1 private video could not be imported
|
||||
|
||||
const { data: videos } = await remoteServer.playlists.listVideos({ playlistId: playlist1.id, token: remoteNoahToken })
|
||||
expect(videos[0].position).to.equal(1)
|
||||
expect(videos[0].startTimestamp).to.equal(2)
|
||||
expect(videos[0].stopTimestamp).to.equal(3)
|
||||
expect(videos[0].video).to.not.exist // Mouska is blocked
|
||||
|
||||
expect(videos[1].position).to.equal(2)
|
||||
expect(videos[1].video.uuid).to.equal(noahVideo.uuid)
|
||||
|
||||
// Federated
|
||||
await server.playlists.get({ playlistId: playlist1.uuid })
|
||||
}
|
||||
|
||||
{
|
||||
const playlist2 = data.find(p => p.displayName === 'noah playlist 2')
|
||||
expect(playlist2).to.exist
|
||||
|
||||
expect(playlist2.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
|
||||
expect(playlist2.videosLength).to.equal(0)
|
||||
|
||||
// Federated
|
||||
await server.playlists.get({ playlistId: playlist2.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have correctly imported user videos', async function () {
|
||||
const { data } = await remoteServer.videos.listMyVideos({ token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(4)
|
||||
|
||||
{
|
||||
const privateVideo = data.find(v => v.name === 'noah private video')
|
||||
expect(privateVideo).to.exist
|
||||
expect(privateVideo.privacy.id).to.equal(VideoPrivacy.PRIVATE)
|
||||
|
||||
// Not federated
|
||||
await server.videos.get({ id: privateVideo.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
}
|
||||
|
||||
{
|
||||
const publicVideo = data.find(v => v.name === 'noah public video')
|
||||
expect(publicVideo).to.exist
|
||||
expect(publicVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC)
|
||||
|
||||
// Federated
|
||||
await server.videos.get({ id: publicVideo.uuid })
|
||||
}
|
||||
|
||||
{
|
||||
const passwordVideo = data.find(v => v.name === 'noah password video')
|
||||
expect(passwordVideo).to.exist
|
||||
expect(passwordVideo.privacy.id).to.equal(VideoPrivacy.PASSWORD_PROTECTED)
|
||||
|
||||
const { data: passwords } = await remoteServer.videoPasswords.list({ videoId: passwordVideo.uuid })
|
||||
expect(passwords.map(p => p.password).sort()).to.deep.equal([ 'password1', 'password2' ])
|
||||
|
||||
// Not federated
|
||||
await server.videos.get({ id: passwordVideo.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
}
|
||||
|
||||
{
|
||||
const otherVideo = data.find(v => v.name === 'noah public video second channel')
|
||||
expect(otherVideo).to.exist
|
||||
|
||||
for (const checkServer of [ server, remoteServer ]) {
|
||||
await completeVideoCheck({
|
||||
server: checkServer,
|
||||
originServer: remoteServer,
|
||||
videoUUID: otherVideo.uuid,
|
||||
objectStorageBaseUrl: objectStorage?.getMockWebVideosBaseUrl(),
|
||||
|
||||
attributes: {
|
||||
name: 'noah public video second channel',
|
||||
privacy: (VideoPrivacy.PUBLIC),
|
||||
category: (12),
|
||||
tags: [ 'tag1', 'tag2' ],
|
||||
commentsEnabled: false,
|
||||
downloadEnabled: false,
|
||||
nsfw: false,
|
||||
description: ('video description'),
|
||||
support: ('video support'),
|
||||
language: 'fr',
|
||||
licence: 1,
|
||||
originallyPublishedAt: new Date(0).toISOString(),
|
||||
account: {
|
||||
name: 'noah_remote',
|
||||
host: remoteServer.host
|
||||
},
|
||||
isLocal: checkServer === remoteServer,
|
||||
likes: 0,
|
||||
dislikes: 0,
|
||||
duration: 5,
|
||||
channel: {
|
||||
displayName: 'noah display name',
|
||||
name: 'noah_second_channel',
|
||||
description: 'noah description',
|
||||
isLocal: checkServer === remoteServer
|
||||
},
|
||||
fixture: 'video_short.webm',
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
size: 61000
|
||||
},
|
||||
{
|
||||
resolution: 480,
|
||||
size: 40000
|
||||
},
|
||||
{
|
||||
resolution: 360,
|
||||
size: 32000
|
||||
},
|
||||
{
|
||||
resolution: 240,
|
||||
size: 23000
|
||||
}
|
||||
],
|
||||
thumbnailfile: 'custom-thumbnail-from-preview',
|
||||
previewfile: 'custom-preview'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
await completeCheckHlsPlaylist({
|
||||
hlsOnly: false,
|
||||
servers: [ remoteServer, server ],
|
||||
videoUUID: otherVideo.uuid,
|
||||
objectStorageBaseUrl: objectStorage?.getMockPlaylistBaseUrl(),
|
||||
resolutions: [ 720, 480, 360, 240 ]
|
||||
})
|
||||
|
||||
const source = await remoteServer.videos.getSource({ id: otherVideo.uuid })
|
||||
expect(source.filename).to.equal('video_short.webm')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should re-import the same file', async function () {
|
||||
this.timeout(240000)
|
||||
|
||||
const { userImport } = await remoteServer.userImports.importArchive({ fixture: archivePath, userId: remoteNoahId })
|
||||
await waitJobs([ remoteServer ])
|
||||
latestImportId = userImport.id
|
||||
})
|
||||
|
||||
it('Should have the status of this new reimport', async function () {
|
||||
const userImport = await remoteServer.userImports.getLatestImport({ userId: remoteNoahId, token: remoteNoahToken })
|
||||
|
||||
expect(userImport.id).to.equal(latestImportId)
|
||||
expect(userImport.state.id).to.equal(UserImportState.COMPLETED)
|
||||
expect(userImport.state.label).to.equal('Completed')
|
||||
})
|
||||
|
||||
it('Should not have duplicated data', async function () {
|
||||
// Blocklist
|
||||
{
|
||||
{
|
||||
const { data } = await remoteServer.blocklist.listMyAccountBlocklist({ start: 0, count: 5, token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(2)
|
||||
}
|
||||
|
||||
{
|
||||
const { data } = await remoteServer.blocklist.listMyServerBlocklist({ start: 0, count: 5, token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(1)
|
||||
}
|
||||
}
|
||||
|
||||
// My avatars
|
||||
{
|
||||
const me = await remoteServer.users.getMyInfo({ token: remoteNoahToken })
|
||||
expect(me.account.avatars).to.have.lengthOf(2)
|
||||
}
|
||||
|
||||
// Channels
|
||||
{
|
||||
const { data: channels } = await remoteServer.channels.listByAccount({ token: remoteNoahToken, accountName: 'noah_remote' })
|
||||
expect(channels).to.have.lengthOf(3)
|
||||
}
|
||||
|
||||
// Following
|
||||
{
|
||||
const { data } = await remoteServer.subscriptions.list({ token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(2)
|
||||
}
|
||||
|
||||
// Likes/dislikes
|
||||
{
|
||||
const video = await remoteServer.videos.get({ id: mouskaVideo.uuid })
|
||||
expect(video.likes).to.equal(2)
|
||||
expect(video.dislikes).to.equal(0)
|
||||
|
||||
const { rating } = await remoteServer.users.getMyRating({ videoId: mouskaVideo.uuid, token: remoteNoahToken })
|
||||
expect(rating).to.equal('like')
|
||||
}
|
||||
|
||||
// Playlists
|
||||
{
|
||||
const { data } = await remoteServer.playlists.listByAccount({ handle: 'noah_remote', token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(3)
|
||||
}
|
||||
|
||||
// Videos
|
||||
{
|
||||
const { data } = await remoteServer.videos.listMyVideos({ token: remoteNoahToken })
|
||||
expect(data).to.have.lengthOf(4)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have received an email on finished import', async function () {
|
||||
const email = emails.reverse().find(e => {
|
||||
return e['to'][0]['address'] === 'noah_remote@example.com' &&
|
||||
e['subject'].includes('archive import has finished')
|
||||
})
|
||||
|
||||
expect(email).to.exist
|
||||
expect(email['text']).to.contain('as considered duplicate: 4') // 4 videos are considered as duplicates
|
||||
})
|
||||
|
||||
it('Should auto blacklist imported videos if enabled by the administrator', async function () {
|
||||
this.timeout(240000)
|
||||
|
||||
await blockedServer.config.enableAutoBlacklist()
|
||||
|
||||
const { token, userId } = await blockedServer.users.generate('blocked_user')
|
||||
await blockedServer.userImports.importArchive({ fixture: archivePath, userId, token })
|
||||
await waitJobs([ blockedServer ])
|
||||
|
||||
{
|
||||
const { data } = await blockedServer.videos.listMyVideos({ token })
|
||||
expect(data).to.have.lengthOf(4)
|
||||
|
||||
for (const video of data) {
|
||||
expect(video.blacklisted).to.be.true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
MockSmtpServer.Instance.kill()
|
||||
|
||||
await cleanupTests([ server, remoteServer, blockedServer ])
|
||||
})
|
||||
}
|
||||
|
||||
describe('Test user import', function () {
|
||||
|
||||
describe('From filesystem', function () {
|
||||
runTest(false)
|
||||
})
|
||||
|
||||
describe('From object storage', function () {
|
||||
if (areMockObjectStorageTestsDisabled()) return
|
||||
|
||||
runTest(true)
|
||||
})
|
||||
})
|
|
@ -42,7 +42,7 @@ describe('Test videos import in a channel', function () {
|
|||
})
|
||||
|
||||
it('These imports should not have a sync id', async function () {
|
||||
const { total, data } = await server.imports.getMyVideoImports()
|
||||
const { total, data } = await server.videoImports.getMyVideoImports()
|
||||
|
||||
expect(total).to.equal(2)
|
||||
expect(data).to.have.lengthOf(2)
|
||||
|
@ -83,7 +83,7 @@ describe('Test videos import in a channel', function () {
|
|||
})
|
||||
|
||||
it('These imports should have a sync id', async function () {
|
||||
const { total, data } = await server.imports.getMyVideoImports()
|
||||
const { total, data } = await server.videoImports.getMyVideoImports()
|
||||
|
||||
expect(total).to.equal(4)
|
||||
expect(data).to.have.lengthOf(4)
|
||||
|
@ -98,7 +98,7 @@ describe('Test videos import in a channel', function () {
|
|||
})
|
||||
|
||||
it('Should be able to filter imports by this sync id', async function () {
|
||||
const { total, data } = await server.imports.getMyVideoImports({ videoChannelSyncId: server.store.videoChannelSync.id })
|
||||
const { total, data } = await server.videoImports.getMyVideoImports({ videoChannelSyncId: server.store.videoChannelSync.id })
|
||||
|
||||
expect(total).to.equal(2)
|
||||
expect(data).to.have.lengthOf(2)
|
||||
|
|
|
@ -42,16 +42,22 @@ describe('Test resumable upload', function () {
|
|||
|
||||
const size = await buildSize(defaultFixture, options.size)
|
||||
|
||||
const attributes = {
|
||||
name: 'video',
|
||||
channelId: options.channelId ?? server.store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
fixture: defaultFixture
|
||||
}
|
||||
|
||||
const mimetype = 'video/mp4'
|
||||
|
||||
const res = await server.videos.prepareResumableUpload({ path, token, attributes, size, mimetype, originalName, lastModified })
|
||||
const res = await server.videos.prepareVideoResumableUpload({
|
||||
path,
|
||||
token,
|
||||
fixture: defaultFixture,
|
||||
fields: {
|
||||
name: 'video',
|
||||
channelId: options.channelId ?? server.store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
},
|
||||
size,
|
||||
mimetype,
|
||||
originalName,
|
||||
lastModified
|
||||
})
|
||||
|
||||
return res.header['location'].split('?')[1]
|
||||
}
|
||||
|
@ -71,7 +77,7 @@ describe('Test resumable upload', function () {
|
|||
const size = await buildSize(defaultFixture, options.size)
|
||||
const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
|
||||
|
||||
return server.videos.sendResumableChunks({
|
||||
return server.videos.sendResumableVideoChunks({
|
||||
token,
|
||||
path,
|
||||
pathUploadId,
|
||||
|
@ -133,7 +139,7 @@ describe('Test resumable upload', function () {
|
|||
it('Should correctly delete files after an upload', async function () {
|
||||
const uploadId = await prepareUpload()
|
||||
await sendChunks({ pathUploadId: uploadId })
|
||||
await server.videos.endResumableUpload({ path, pathUploadId: uploadId })
|
||||
await server.videos.endVideoResumableUpload({ path, pathUploadId: uploadId })
|
||||
|
||||
expect(await countResumableUploads()).to.equal(0)
|
||||
})
|
||||
|
|
|
@ -92,7 +92,7 @@ describe('Test channel synchronizations', function () {
|
|||
this.timeout(120_000)
|
||||
|
||||
{
|
||||
const { video } = await servers[0].imports.importVideo({
|
||||
const { video } = await servers[0].videoImports.importVideo({
|
||||
attributes: {
|
||||
channelId: servers[0].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
|
@ -210,7 +210,7 @@ describe('Test channel synchronizations', function () {
|
|||
})
|
||||
|
||||
it('Should list imports of a channel synchronization', async function () {
|
||||
const { total, data } = await servers[0].imports.getMyVideoImports({ videoChannelSyncId: rootChannelSyncId })
|
||||
const { total, data } = await servers[0].videoImports.getMyVideoImports({ videoChannelSyncId: rootChannelSyncId })
|
||||
|
||||
expect(total).to.equal(1)
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
|
|
@ -237,7 +237,7 @@ describe('Test video chapters', function () {
|
|||
targetUrl: FIXTURE_URLS.youtubeChapters,
|
||||
description: 'this is a super description\n'
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
@ -277,7 +277,7 @@ describe('Test video chapters', function () {
|
|||
'00:03 chapter 2\n' +
|
||||
'00:04 chapter 3\n'
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
@ -309,7 +309,7 @@ describe('Test video chapters', function () {
|
|||
privacy: VideoPrivacy.PUBLIC,
|
||||
targetUrl: FIXTURE_URLS.chatersVideo
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ describe('Test video imports', function () {
|
|||
|
||||
{
|
||||
const attributes = { ...baseAttributes, targetUrl: FIXTURE_URLS.youtube }
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
expect(video.name).to.equal('small video - youtube')
|
||||
|
||||
{
|
||||
|
@ -174,7 +174,7 @@ describe('Test video imports', function () {
|
|||
description: 'this is a super torrent description',
|
||||
tags: [ 'tag_torrent1', 'tag_torrent2' ]
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
expect(video.name).to.equal('super peertube2 video')
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ describe('Test video imports', function () {
|
|||
description: 'this is a super torrent description',
|
||||
tags: [ 'tag_torrent1', 'tag_torrent2' ]
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
expect(video.name).to.equal('你好 世界 720p.mp4')
|
||||
}
|
||||
})
|
||||
|
@ -202,7 +202,7 @@ describe('Test video imports', function () {
|
|||
})
|
||||
|
||||
it('Should list the videos to import in my imports on server 1', async function () {
|
||||
const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ sort: '-createdAt' })
|
||||
const { total, data: videoImports } = await servers[0].videoImports.getMyVideoImports({ sort: '-createdAt' })
|
||||
expect(total).to.equal(3)
|
||||
|
||||
expect(videoImports).to.have.lengthOf(3)
|
||||
|
@ -224,7 +224,7 @@ describe('Test video imports', function () {
|
|||
})
|
||||
|
||||
it('Should filter my imports on target URL', async function () {
|
||||
const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ targetUrl: FIXTURE_URLS.youtube })
|
||||
const { total, data: videoImports } = await servers[0].videoImports.getMyVideoImports({ targetUrl: FIXTURE_URLS.youtube })
|
||||
expect(total).to.equal(1)
|
||||
expect(videoImports).to.have.lengthOf(1)
|
||||
|
||||
|
@ -233,7 +233,7 @@ describe('Test video imports', function () {
|
|||
|
||||
it('Should search in my imports', async function () {
|
||||
{
|
||||
const { total, data } = await servers[0].imports.getMyVideoImports({ search: 'peertube2' })
|
||||
const { total, data } = await servers[0].videoImports.getMyVideoImports({ search: 'peertube2' })
|
||||
expect(total).to.equal(1)
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
||||
|
@ -242,7 +242,7 @@ describe('Test video imports', function () {
|
|||
}
|
||||
|
||||
{
|
||||
const { total, data } = await servers[0].imports.getMyVideoImports({ search: FIXTURE_URLS.magnet })
|
||||
const { total, data } = await servers[0].videoImports.getMyVideoImports({ search: FIXTURE_URLS.magnet })
|
||||
expect(total).to.equal(1)
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
||||
|
@ -269,7 +269,7 @@ describe('Test video imports', function () {
|
|||
it('Should import a video on server 2 with some fields', async function () {
|
||||
this.timeout(60_000)
|
||||
|
||||
const { video } = await servers[1].imports.importVideo({
|
||||
const { video } = await servers[1].videoImports.importVideo({
|
||||
attributes: {
|
||||
targetUrl: FIXTURE_URLS.youtube,
|
||||
channelId: servers[1].store.channel.id,
|
||||
|
@ -312,7 +312,7 @@ describe('Test video imports', function () {
|
|||
channelId: servers[1].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video } = await servers[1].imports.importVideo({ attributes })
|
||||
const { video } = await servers[1].videoImports.importVideo({ attributes })
|
||||
const videoUUID = video.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -354,7 +354,7 @@ describe('Test video imports', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: videoImported } = await servers[0].videoImports.importVideo({ attributes })
|
||||
const videoUUID = videoImported.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -394,7 +394,7 @@ describe('Test video imports', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: videoImported } = await servers[0].videoImports.importVideo({ attributes })
|
||||
const videoUUID = videoImported.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -422,7 +422,7 @@ describe('Test video imports', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video: videoImported } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: videoImported } = await servers[0].videoImports.importVideo({ attributes })
|
||||
const videoUUID = videoImported.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -454,7 +454,7 @@ describe('Test video imports', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||
const videoUUID = video.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
@ -497,7 +497,7 @@ describe('Test video imports', function () {
|
|||
|
||||
async function importVideo (name: string) {
|
||||
const attributes = { name, channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
|
||||
const res = await server.imports.importVideo({ attributes })
|
||||
const res = await server.videoImports.importVideo({ attributes })
|
||||
|
||||
return res.id
|
||||
}
|
||||
|
@ -516,16 +516,16 @@ describe('Test video imports', function () {
|
|||
await server.jobs.pauseJobQueue()
|
||||
pendingImportId = await importVideo('pending')
|
||||
|
||||
const { data } = await server.imports.getMyVideoImports()
|
||||
const { data } = await server.videoImports.getMyVideoImports()
|
||||
expect(data).to.have.lengthOf(2)
|
||||
|
||||
finishedVideo = data.find(i => i.id === finishedImportId).video
|
||||
})
|
||||
|
||||
it('Should delete a video import', async function () {
|
||||
await server.imports.delete({ importId: finishedImportId })
|
||||
await server.videoImports.delete({ importId: finishedImportId })
|
||||
|
||||
const { data } = await server.imports.getMyVideoImports()
|
||||
const { data } = await server.videoImports.getMyVideoImports()
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[0].id).to.equal(pendingImportId)
|
||||
expect(data[0].state.id).to.equal(VideoImportState.PENDING)
|
||||
|
@ -538,9 +538,9 @@ describe('Test video imports', function () {
|
|||
})
|
||||
|
||||
it('Should cancel a video import', async function () {
|
||||
await server.imports.cancel({ importId: pendingImportId })
|
||||
await server.videoImports.cancel({ importId: pendingImportId })
|
||||
|
||||
const { data } = await server.imports.getMyVideoImports()
|
||||
const { data } = await server.videoImports.getMyVideoImports()
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[0].id).to.equal(pendingImportId)
|
||||
expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
|
||||
|
@ -553,7 +553,7 @@ describe('Test video imports', function () {
|
|||
|
||||
await waitJobs([ server ])
|
||||
|
||||
const { data } = await server.imports.getMyVideoImports()
|
||||
const { data } = await server.videoImports.getMyVideoImports()
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[0].id).to.equal(pendingImportId)
|
||||
expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
|
||||
|
@ -561,8 +561,8 @@ describe('Test video imports', function () {
|
|||
})
|
||||
|
||||
it('Should delete the cancelled video import', async function () {
|
||||
await server.imports.delete({ importId: pendingImportId })
|
||||
const { data } = await server.imports.getMyVideoImports()
|
||||
await server.videoImports.delete({ importId: pendingImportId })
|
||||
const { data } = await server.videoImports.getMyVideoImports()
|
||||
expect(data).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
|
@ -581,7 +581,7 @@ describe('Test video imports', function () {
|
|||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
|
||||
return server.imports.importVideo({ attributes })
|
||||
return server.videoImports.importVideo({ attributes })
|
||||
}
|
||||
|
||||
async function testBinaryUpdate (releaseUrl: string, releaseName: string) {
|
||||
|
|
|
@ -263,20 +263,6 @@ describe('Test a video file replacement', function () {
|
|||
|
||||
describe('Autoblacklist', function () {
|
||||
|
||||
function updateAutoBlacklist (enabled: boolean) {
|
||||
return servers[0].config.updateExistingSubConfig({
|
||||
newConfig: {
|
||||
autoBlacklist: {
|
||||
videos: {
|
||||
ofUsers: {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function expectBlacklist (uuid: string, value: boolean) {
|
||||
const video = await servers[0].videos.getWithToken({ id: uuid })
|
||||
|
||||
|
@ -284,7 +270,7 @@ describe('Test a video file replacement', function () {
|
|||
}
|
||||
|
||||
before(async function () {
|
||||
await updateAutoBlacklist(true)
|
||||
await servers[0].config.enableAutoBlacklist()
|
||||
})
|
||||
|
||||
it('Should auto blacklist an unblacklisted video after file replacement', async function () {
|
||||
|
@ -326,7 +312,7 @@ describe('Test a video file replacement', function () {
|
|||
await servers[0].blacklist.remove({ videoId: uuid })
|
||||
await expectBlacklist(uuid, false)
|
||||
|
||||
await updateAutoBlacklist(false)
|
||||
await servers[0].config.disableAutoBlacklist()
|
||||
|
||||
await servers[0].videos.replaceSourceFile({ videoId: uuid, token: userToken, fixture: 'video_short1.webm' })
|
||||
await waitJobs(servers)
|
||||
|
|
|
@ -126,7 +126,7 @@ describe('Test video storyboard', function () {
|
|||
if (areHttpImportTestsDisabled()) return
|
||||
|
||||
// 3s video
|
||||
const { video } = await servers[0].imports.importVideo({
|
||||
const { video } = await servers[0].videoImports.importVideo({
|
||||
attributes: {
|
||||
targetUrl: FIXTURE_URLS.goodVideo,
|
||||
channelId: servers[0].store.channel.id,
|
||||
|
@ -146,7 +146,7 @@ describe('Test video storyboard', function () {
|
|||
if (areHttpImportTestsDisabled()) return
|
||||
|
||||
// 10s video
|
||||
const { video } = await servers[0].imports.importVideo({
|
||||
const { video } = await servers[0].videoImports.importVideo({
|
||||
attributes: {
|
||||
magnetUri: FIXTURE_URLS.magnet,
|
||||
channelId: servers[0].store.channel.id,
|
||||
|
|
|
@ -24,12 +24,14 @@ import {
|
|||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { FIXTURE_URLS } from '../shared/tests.js'
|
||||
import { expectEndWith } from '@tests/shared/checks.js'
|
||||
|
||||
describe('Test plugin filter hooks', function () {
|
||||
let servers: PeerTubeServer[]
|
||||
let videoUUID: string
|
||||
let threadId: number
|
||||
let videoPlaylistUUID: string
|
||||
let importUserToken: string
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
@ -78,8 +80,18 @@ describe('Test plugin filter hooks', function () {
|
|||
}
|
||||
})
|
||||
|
||||
{
|
||||
const { userId, token } = await servers[0].users.generate('to_import')
|
||||
importUserToken = token
|
||||
await servers[0].users.update({ userId, videoQuota: -1, videoQuotaDaily: -1 })
|
||||
|
||||
await servers[0].userImports.importArchive({ userId, token, fixture: 'export-with-files.zip' })
|
||||
}
|
||||
|
||||
// Root subscribes to itself
|
||||
await servers[0].subscriptions.add({ targetUri: 'root_channel@' + servers[0].host })
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
describe('Videos', function () {
|
||||
|
@ -95,7 +107,7 @@ describe('Test plugin filter hooks', function () {
|
|||
const { total } = await servers[0].videos.list({ start: 0, count: 0 })
|
||||
|
||||
// Plugin do +1 to the total result
|
||||
expect(total).to.equal(11)
|
||||
expect(total).to.equal(12)
|
||||
})
|
||||
|
||||
it('Should run filter:api.video-playlist.videos.list.params', async function () {
|
||||
|
@ -215,7 +227,7 @@ describe('Test plugin filter hooks', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
targetUrl: FIXTURE_URLS.goodVideo + 'bad'
|
||||
}
|
||||
await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
await servers[0].videoImports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
})
|
||||
|
||||
it('Should run filter:api.video.pre-import-torrent.accept.result', async function () {
|
||||
|
@ -225,7 +237,7 @@ describe('Test plugin filter hooks', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
torrentfile: 'video-720p.torrent' as any
|
||||
}
|
||||
await servers[0].imports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
await servers[0].videoImports.importVideo({ attributes, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||
})
|
||||
|
||||
it('Should run filter:api.video.post-import-url.accept.result', async function () {
|
||||
|
@ -240,14 +252,14 @@ describe('Test plugin filter hooks', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
targetUrl: FIXTURE_URLS.goodVideo
|
||||
}
|
||||
const body = await servers[0].imports.importVideo({ attributes })
|
||||
const body = await servers[0].videoImports.importVideo({ attributes })
|
||||
videoImportId = body.id
|
||||
}
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
{
|
||||
const body = await servers[0].imports.getMyVideoImports()
|
||||
const body = await servers[0].videoImports.getMyVideoImports()
|
||||
const videoImports = body.data
|
||||
|
||||
const videoImport = videoImports.find(i => i.id === videoImportId)
|
||||
|
@ -269,14 +281,14 @@ describe('Test plugin filter hooks', function () {
|
|||
channelId: servers[0].store.channel.id,
|
||||
torrentfile: 'video-720p.torrent' as any
|
||||
}
|
||||
const body = await servers[0].imports.importVideo({ attributes })
|
||||
const body = await servers[0].videoImports.importVideo({ attributes })
|
||||
videoImportId = body.id
|
||||
}
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
{
|
||||
const { data: videoImports } = await servers[0].imports.getMyVideoImports()
|
||||
const { data: videoImports } = await servers[0].videoImports.getMyVideoImports()
|
||||
|
||||
const videoImport = videoImports.find(i => i.id === videoImportId)
|
||||
|
||||
|
@ -284,6 +296,14 @@ describe('Test plugin filter hooks', function () {
|
|||
expect(videoImport.state.label).to.equal('Rejected')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should run filter:api.video.user-import.video-attribute.result', async function () {
|
||||
const { data } = await servers[0].videos.listMyVideos({ token: importUserToken })
|
||||
expect(data).to.have.lengthOf(1)
|
||||
|
||||
// We filter out video 1 in the plugin
|
||||
expect(data[0].name).to.not.equal('video 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Video comments accept', function () {
|
||||
|
@ -413,7 +433,7 @@ describe('Test plugin filter hooks', function () {
|
|||
targetUrl: FIXTURE_URLS.goodVideo,
|
||||
channelId: servers[0].store.channel.id
|
||||
}
|
||||
const body = await servers[0].imports.importVideo({ attributes })
|
||||
const body = await servers[0].videoImports.importVideo({ attributes })
|
||||
await checkIsBlacklisted(body.video.uuid, true)
|
||||
})
|
||||
|
||||
|
@ -739,7 +759,7 @@ describe('Test plugin filter hooks', function () {
|
|||
|
||||
before(async function () {
|
||||
await servers[0].config.enableLive({ transcoding: false, allowReplay: false })
|
||||
await servers[0].config.enableImports()
|
||||
await servers[0].config.enableVideoImports()
|
||||
await servers[0].config.disableTranscoding()
|
||||
})
|
||||
|
||||
|
@ -760,7 +780,7 @@ describe('Test plugin filter hooks', function () {
|
|||
targetUrl: FIXTURE_URLS.goodVideo,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video: { id } } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: { id } } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
const video = await servers[0].videos.get({ id })
|
||||
expect(video.description).to.equal('import url - filter:api.video.import-url.video-attribute.result')
|
||||
|
@ -774,7 +794,7 @@ describe('Test plugin filter hooks', function () {
|
|||
magnetUri: FIXTURE_URLS.magnet,
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
const { video: { id } } = await servers[0].imports.importVideo({ attributes })
|
||||
const { video: { id } } = await servers[0].videoImports.importVideo({ attributes })
|
||||
|
||||
const video = await servers[0].videos.get({ id })
|
||||
expect(video.description).to.equal('import torrent - filter:api.video.import-torrent.video-attribute.result')
|
||||
|
@ -792,6 +812,16 @@ describe('Test plugin filter hooks', function () {
|
|||
const video = await servers[0].videos.get({ id })
|
||||
expect(video.description).to.equal('live - filter:api.video.live.video-attribute.result')
|
||||
})
|
||||
|
||||
it('Should run filter:api.video.user-import.video-attribute.result', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
const { data } = await servers[0].videos.listMyVideos({ token: importUserToken })
|
||||
|
||||
for (const video of data) {
|
||||
expectEndWith(video.description, ' - filter:api.video.user-import.video-attribute.result')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Stats filters', function () {
|
||||
|
@ -876,7 +906,7 @@ describe('Test plugin filter hooks', function () {
|
|||
const { total } = await servers[0].channels.list({ start: 0, count: 1 })
|
||||
|
||||
// plugin do +1 to the total parameter
|
||||
expect(total).to.equal(4)
|
||||
expect(total).to.equal(6)
|
||||
})
|
||||
|
||||
it('Should run filter:api.video-channel.get.result', async function () {
|
||||
|
|
298
packages/tests/src/shared/import-export.ts
Normal file
298
packages/tests/src/shared/import-export.ts
Normal file
|
@ -0,0 +1,298 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
import {
|
||||
ActivityCreate,
|
||||
ActivityPubOrderedCollection,
|
||||
HttpStatusCode,
|
||||
UserExport,
|
||||
UserNotificationSettingValue,
|
||||
VideoCommentObject,
|
||||
VideoObject,
|
||||
VideoPlaylistPrivacy,
|
||||
VideoPrivacy
|
||||
} from '@peertube/peertube-models'
|
||||
import {
|
||||
ConfigCommand,
|
||||
ObjectStorageCommand,
|
||||
PeerTubeServer,
|
||||
createSingleServer,
|
||||
doubleFollow, makeRawRequest,
|
||||
setAccessTokensToServers,
|
||||
setDefaultVideoChannel,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
import JSZip from 'jszip'
|
||||
import { resolve } from 'path'
|
||||
import { MockSmtpServer } from './mock-servers/mock-email.js'
|
||||
import { getAllNotificationsSettings } from './notifications.js'
|
||||
import { getFilenameFromUrl } from '@peertube/peertube-node-utils'
|
||||
import { testFileExistsOrNot } from './checks.js'
|
||||
|
||||
type ExportOutbox = ActivityPubOrderedCollection<ActivityCreate<VideoObject | VideoCommentObject>>
|
||||
|
||||
export async function downloadZIP (server: PeerTubeServer, userId: number) {
|
||||
const { data } = await server.userExports.list({ userId })
|
||||
|
||||
const res = await makeRawRequest({
|
||||
url: data[0].privateDownloadUrl,
|
||||
responseType: 'arraybuffer',
|
||||
redirects: 1,
|
||||
expectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
|
||||
return JSZip.loadAsync(res.body)
|
||||
}
|
||||
|
||||
export async function parseZIPJSONFile <T> (zip: JSZip, path: string) {
|
||||
return JSON.parse(await zip.file(path).async('string')) as T
|
||||
}
|
||||
|
||||
export async function checkFileExistsInZIP (zip: JSZip, path: string, base = '/') {
|
||||
const innerPath = resolve(base, path).substring(1) // Remove '/' at the beginning of the string
|
||||
|
||||
expect(zip.files[innerPath], `${innerPath} does not exist`).to.exist
|
||||
|
||||
const buf = await zip.file(innerPath).async('arraybuffer')
|
||||
expect(buf.byteLength, `${innerPath} is empty`).to.be.greaterThan(0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function parseAPOutbox (zip: JSZip) {
|
||||
return parseZIPJSONFile<ExportOutbox>(zip, 'activity-pub/outbox.json')
|
||||
}
|
||||
|
||||
export function findVideoObjectInOutbox (outbox: ExportOutbox, videoName: string) {
|
||||
return outbox.orderedItems.find(i => {
|
||||
return i.type === 'Create' && i.object.type === 'Video' && i.object.name === videoName
|
||||
}) as ActivityCreate<VideoObject>
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function regenerateExport (options: {
|
||||
server: PeerTubeServer
|
||||
userId: number
|
||||
withVideoFiles: boolean
|
||||
}) {
|
||||
const { server, userId, withVideoFiles } = options
|
||||
|
||||
await server.userExports.deleteAllArchives({ userId })
|
||||
const res = await server.userExports.request({ userId, withVideoFiles })
|
||||
await server.userExports.waitForCreation({ userId })
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export async function checkExportFileExists (options: {
|
||||
server: PeerTubeServer
|
||||
userExport: UserExport
|
||||
redirectedUrl: string
|
||||
exists: boolean
|
||||
withObjectStorage: boolean
|
||||
}) {
|
||||
const { server, exists, userExport, redirectedUrl, withObjectStorage } = options
|
||||
|
||||
const filename = getFilenameFromUrl(userExport.privateDownloadUrl)
|
||||
|
||||
if (exists === true) {
|
||||
if (withObjectStorage) {
|
||||
return makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.OK_200 })
|
||||
}
|
||||
|
||||
return testFileExistsOrNot(server, 'tmp-persistent', filename, true)
|
||||
}
|
||||
|
||||
await testFileExistsOrNot(server, 'tmp-persistent', filename, false)
|
||||
|
||||
if (withObjectStorage) {
|
||||
await makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareImportExportTests (options: {
|
||||
objectStorage: ObjectStorageCommand
|
||||
emails: object[]
|
||||
withBlockedServer: boolean
|
||||
}) {
|
||||
const { emails, objectStorage, withBlockedServer } = options
|
||||
|
||||
let objectStorageConfig: any = {}
|
||||
if (objectStorage) {
|
||||
await objectStorage.prepareDefaultMockBuckets()
|
||||
|
||||
objectStorageConfig = objectStorage.getDefaultMockConfig()
|
||||
}
|
||||
|
||||
const emailPort = await MockSmtpServer.Instance.collectEmails(emails)
|
||||
|
||||
const [ server, remoteServer, blockedServer ] = await Promise.all([
|
||||
await createSingleServer(1, { ...objectStorageConfig, ...ConfigCommand.getEmailOverrideConfig(emailPort) }),
|
||||
await createSingleServer(2, { ...objectStorageConfig, ...ConfigCommand.getEmailOverrideConfig(emailPort) }),
|
||||
|
||||
withBlockedServer
|
||||
? await createSingleServer(3)
|
||||
: Promise.resolve(undefined)
|
||||
])
|
||||
|
||||
const servers = [ server, remoteServer, blockedServer ].filter(s => !!s)
|
||||
|
||||
await setAccessTokensToServers(servers)
|
||||
await setDefaultVideoChannel(servers)
|
||||
|
||||
await Promise.all([
|
||||
await doubleFollow(server, remoteServer),
|
||||
|
||||
withBlockedServer
|
||||
? await doubleFollow(server, blockedServer)
|
||||
: Promise.resolve(undefined),
|
||||
|
||||
withBlockedServer
|
||||
? await doubleFollow(remoteServer, blockedServer)
|
||||
: Promise.resolve(undefined)
|
||||
])
|
||||
|
||||
const mouskaToken = await server.users.generateUserAndToken('mouska')
|
||||
const noahToken = await server.users.generateUserAndToken('noah')
|
||||
const remoteNoahToken = await remoteServer.users.generateUserAndToken('noah_remote')
|
||||
|
||||
// Channel
|
||||
const { id: noahSecondChannelId } = await server.channels.create({
|
||||
token: noahToken,
|
||||
attributes: {
|
||||
name: 'noah_second_channel',
|
||||
displayName: 'noah display name',
|
||||
description: 'noah description',
|
||||
support: 'noah support'
|
||||
}
|
||||
})
|
||||
|
||||
await server.channels.updateImage({
|
||||
channelName: 'noah_second_channel',
|
||||
fixture: 'banner.jpg',
|
||||
type: 'banner'
|
||||
})
|
||||
|
||||
await server.channels.updateImage({
|
||||
channelName: 'noah_second_channel',
|
||||
fixture: 'avatar.png',
|
||||
type: 'avatar'
|
||||
})
|
||||
|
||||
// Videos
|
||||
const externalVideo = await remoteServer.videos.quickUpload({ name: 'external video', privacy: VideoPrivacy.PUBLIC })
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const noahPrivateVideo = await server.videos.quickUpload({ name: 'noah private video', token: noahToken, privacy: VideoPrivacy.PRIVATE })
|
||||
const noahVideo = await server.videos.quickUpload({ name: 'noah public video', token: noahToken, privacy: VideoPrivacy.PUBLIC })
|
||||
// eslint-disable-next-line max-len
|
||||
await server.videos.upload({
|
||||
token: noahToken,
|
||||
attributes: {
|
||||
fixture: 'video_short.webm',
|
||||
name: 'noah public video second channel',
|
||||
category: 12,
|
||||
tags: [ 'tag1', 'tag2' ],
|
||||
commentsEnabled: false,
|
||||
description: 'video description',
|
||||
downloadEnabled: false,
|
||||
language: 'fr',
|
||||
licence: 1,
|
||||
nsfw: false,
|
||||
originallyPublishedAt: new Date(0).toISOString(),
|
||||
support: 'video support',
|
||||
waitTranscoding: true,
|
||||
channelId: noahSecondChannelId,
|
||||
privacy: VideoPrivacy.PUBLIC,
|
||||
thumbnailfile: 'custom-thumbnail.jpg',
|
||||
previewfile: 'custom-preview.jpg'
|
||||
}
|
||||
})
|
||||
|
||||
await server.videos.quickUpload({ name: 'mouska private video', token: mouskaToken, privacy: VideoPrivacy.PRIVATE })
|
||||
const mouskaVideo = await server.videos.quickUpload({ name: 'mouska public video', token: mouskaToken, privacy: VideoPrivacy.PUBLIC })
|
||||
|
||||
// Captions
|
||||
await server.captions.add({ language: 'ar', videoId: noahVideo.uuid, fixture: 'subtitle-good1.vtt' })
|
||||
await server.captions.add({ language: 'fr', videoId: noahVideo.uuid, fixture: 'subtitle-good1.vtt' })
|
||||
|
||||
// My settings
|
||||
await server.users.updateMe({ token: noahToken, description: 'super noah description', p2pEnabled: false })
|
||||
|
||||
// My notification settings
|
||||
await server.notifications.updateMySettings({
|
||||
token: noahToken,
|
||||
|
||||
settings: {
|
||||
...getAllNotificationsSettings(),
|
||||
|
||||
myVideoPublished: UserNotificationSettingValue.NONE,
|
||||
commentMention: UserNotificationSettingValue.EMAIL
|
||||
}
|
||||
})
|
||||
|
||||
// Rate
|
||||
await waitJobs([ server, remoteServer ])
|
||||
|
||||
await server.videos.rate({ id: mouskaVideo.uuid, token: noahToken, rating: 'like' })
|
||||
await server.videos.rate({ id: noahVideo.uuid, token: noahToken, rating: 'like' })
|
||||
await server.videos.rate({ id: externalVideo.uuid, token: noahToken, rating: 'dislike' })
|
||||
|
||||
await server.videos.rate({ id: noahVideo.uuid, token: mouskaToken, rating: 'like' })
|
||||
|
||||
// 2 followers
|
||||
await remoteServer.subscriptions.add({ targetUri: 'noah_channel@' + server.host })
|
||||
await server.subscriptions.add({ targetUri: 'noah_channel@' + server.host })
|
||||
|
||||
// 2 following
|
||||
await server.subscriptions.add({ token: noahToken, targetUri: 'mouska_channel@' + server.host })
|
||||
await server.subscriptions.add({ token: noahToken, targetUri: 'root_channel@' + remoteServer.host })
|
||||
|
||||
// 2 playlists
|
||||
await server.playlists.quickCreate({ displayName: 'root playlist' })
|
||||
const noahPlaylist = await server.playlists.quickCreate({ displayName: 'noah playlist 1', token: noahToken })
|
||||
await server.playlists.quickCreate({ displayName: 'noah playlist 2', token: noahToken, privacy: VideoPlaylistPrivacy.PRIVATE })
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
await server.playlists.addElement({ playlistId: noahPlaylist.uuid, token: noahToken, attributes: { videoId: mouskaVideo.uuid, startTimestamp: 2, stopTimestamp: 3 } })
|
||||
await server.playlists.addElement({ playlistId: noahPlaylist.uuid, token: noahToken, attributes: { videoId: noahVideo.uuid } })
|
||||
await server.playlists.addElement({ playlistId: noahPlaylist.uuid, token: noahToken, attributes: { videoId: noahPrivateVideo.uuid } })
|
||||
|
||||
// 3 threads and some replies
|
||||
await remoteServer.comments.createThread({ videoId: noahVideo.uuid, text: 'remote comment' })
|
||||
await waitJobs([ server, remoteServer ])
|
||||
|
||||
await server.comments.createThread({ videoId: noahVideo.uuid, text: 'local comment' })
|
||||
await server.comments.addReplyToLastThread({ token: noahToken, text: 'noah reply' })
|
||||
|
||||
await server.comments.createThread({ videoId: mouskaVideo.uuid, token: noahToken, text: 'noah comment' })
|
||||
|
||||
// Fetch user ids
|
||||
const rootId = (await server.users.getMyInfo()).id
|
||||
const noahId = (await server.users.getMyInfo({ token: noahToken })).id
|
||||
const remoteRootId = (await remoteServer.users.getMyInfo()).id
|
||||
const remoteNoahId = (await remoteServer.users.getMyInfo({ token: remoteNoahToken })).id
|
||||
|
||||
return {
|
||||
rootId,
|
||||
|
||||
mouskaToken,
|
||||
mouskaVideo,
|
||||
|
||||
remoteRootId,
|
||||
remoteNoahId,
|
||||
remoteNoahToken,
|
||||
|
||||
externalVideo,
|
||||
|
||||
noahId,
|
||||
noahToken,
|
||||
noahPlaylist,
|
||||
noahPrivateVideo,
|
||||
noahVideo,
|
||||
|
||||
server,
|
||||
remoteServer,
|
||||
blockedServer
|
||||
}
|
||||
}
|
|
@ -113,6 +113,8 @@ async function completeVideoCheck (options: {
|
|||
server: PeerTubeServer
|
||||
originServer: PeerTubeServer
|
||||
videoUUID: string
|
||||
objectStorageBaseUrl?: string
|
||||
|
||||
attributes: {
|
||||
name: string
|
||||
category: number
|
||||
|
@ -150,7 +152,7 @@ async function completeVideoCheck (options: {
|
|||
previewfile?: string
|
||||
}
|
||||
}) {
|
||||
const { attributes, originServer, server, videoUUID } = options
|
||||
const { attributes, originServer, server, videoUUID, objectStorageBaseUrl } = options
|
||||
|
||||
await loadLanguages()
|
||||
|
||||
|
@ -215,7 +217,13 @@ async function completeVideoCheck (options: {
|
|||
await testImageGeneratedByFFmpeg(server.url, attributes.previewfile, video.previewPath)
|
||||
}
|
||||
|
||||
await completeWebVideoFilesCheck({ server, originServer, videoUUID: video.uuid, ...pick(attributes, [ 'fixture', 'files' ]) })
|
||||
await completeWebVideoFilesCheck({
|
||||
server,
|
||||
originServer,
|
||||
videoUUID: video.uuid,
|
||||
objectStorageBaseUrl,
|
||||
...pick(attributes, [ 'fixture', 'files' ])
|
||||
})
|
||||
}
|
||||
|
||||
async function checkVideoFilesWereRemoved (options: {
|
||||
|
@ -290,9 +298,11 @@ function checkUploadVideoParam (options: {
|
|||
|
||||
return mode === 'legacy'
|
||||
? server.videos.buildLegacyUpload({ token, attributes, expectedStatus: expectedStatus || completedExpectedStatus })
|
||||
: server.videos.buildResumeUpload({
|
||||
: server.videos.buildResumeVideoUpload({
|
||||
token,
|
||||
attributes,
|
||||
fixture: attributes.fixture,
|
||||
attaches: this.buildUploadAttaches(attributes),
|
||||
fields: this.buildUploadFields(attributes),
|
||||
expectedStatus,
|
||||
completedExpectedStatus,
|
||||
path: '/api/v1/videos/upload-resumable'
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
||||
"paths": {
|
||||
"@tests/*": [ "src/*" ],
|
||||
"@tests/*": [ "./src/*" ],
|
||||
"@server/*": [ "../../server/core/*" ]
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue