mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-04 18:29:27 +02:00
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 { decode } from 'querystring'
|
||||||
import request from 'supertest'
|
import request from 'supertest'
|
||||||
import { URL } from 'url'
|
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 { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models'
|
||||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||||
|
|
||||||
|
@ -23,23 +23,33 @@ export type CommonRequestParams = {
|
||||||
expectedStatus?: HttpStatusCodeType
|
expectedStatus?: HttpStatusCodeType
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRawRequest (options: {
|
export function makeRawRequest (options: {
|
||||||
url: string
|
url: string
|
||||||
token?: string
|
token?: string
|
||||||
expectedStatus?: HttpStatusCodeType
|
expectedStatus?: HttpStatusCodeType
|
||||||
|
responseType?: string
|
||||||
range?: string
|
range?: string
|
||||||
query?: { [ id: string ]: string }
|
query?: { [ id: string ]: string }
|
||||||
method?: 'GET' | 'POST'
|
method?: 'GET' | 'POST'
|
||||||
|
accept?: string
|
||||||
headers?: { [ name: string ]: 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 = {
|
const reqOptions = {
|
||||||
url: `${protocol}//${host}`,
|
url: `${protocol}//${host}`,
|
||||||
path: pathname,
|
path: pathname,
|
||||||
|
|
||||||
contentType: undefined,
|
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') {
|
if (options.method === 'POST') {
|
||||||
|
@ -49,7 +59,7 @@ function makeRawRequest (options: {
|
||||||
return makeGetRequest(reqOptions)
|
return makeGetRequest(reqOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeGetRequest (options: CommonRequestParams & {
|
export function makeGetRequest (options: CommonRequestParams & {
|
||||||
query?: any
|
query?: any
|
||||||
rawQuery?: string
|
rawQuery?: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -61,7 +71,7 @@ function makeGetRequest (options: CommonRequestParams & {
|
||||||
return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
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({
|
return makeGetRequest({
|
||||||
url,
|
url,
|
||||||
path,
|
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({
|
return makeGetRequest({
|
||||||
url,
|
url,
|
||||||
path,
|
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
|
query?: any
|
||||||
rawQuery?: string
|
rawQuery?: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -91,7 +113,7 @@ function makeDeleteRequest (options: CommonRequestParams & {
|
||||||
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeUploadRequest (options: CommonRequestParams & {
|
export function makeUploadRequest (options: CommonRequestParams & {
|
||||||
method?: 'POST' | 'PUT'
|
method?: 'POST' | 'PUT'
|
||||||
|
|
||||||
fields: { [ fieldName: string ]: any }
|
fields: { [ fieldName: string ]: any }
|
||||||
|
@ -119,7 +141,7 @@ function makeUploadRequest (options: CommonRequestParams & {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
function makePostBodyRequest (options: CommonRequestParams & {
|
export function makePostBodyRequest (options: CommonRequestParams & {
|
||||||
fields?: { [ fieldName: string ]: any }
|
fields?: { [ fieldName: string ]: any }
|
||||||
}) {
|
}) {
|
||||||
const req = request(options.url).post(options.path)
|
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 })
|
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
function makePutBodyRequest (options: {
|
export function makePutBodyRequest (options: {
|
||||||
url: string
|
url: string
|
||||||
path: string
|
path: string
|
||||||
token?: string
|
token?: string
|
||||||
|
@ -142,21 +164,35 @@ function makePutBodyRequest (options: {
|
||||||
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...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])
|
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)
|
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)
|
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 => {
|
return test.then(res => {
|
||||||
if (res.body instanceof Buffer) {
|
if (res.body instanceof Buffer) {
|
||||||
try {
|
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))
|
return test.then(res => res.text || new TextDecoder().decode(res.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
makeHTMLRequest,
|
|
||||||
makeGetRequest,
|
|
||||||
decodeQueryString,
|
|
||||||
makeUploadRequest,
|
|
||||||
makePostBodyRequest,
|
|
||||||
makePutBodyRequest,
|
|
||||||
makeDeleteRequest,
|
|
||||||
makeRawRequest,
|
|
||||||
makeActivityPubGetRequest,
|
|
||||||
unwrapBody,
|
|
||||||
unwrapTextOrDecode,
|
|
||||||
unwrapBodyOrDecodeToJSON,
|
|
||||||
unwrapText
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function buildRequest (req: request.Test, options: CommonRequestParams) {
|
function buildRequest (req: request.Test, options: CommonRequestParams) {
|
||||||
|
|
|
@ -46,15 +46,15 @@ export class ConfigCommand extends AbstractCommand {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
disableImports () {
|
disableVideoImports () {
|
||||||
return this.setImportsEnabled(false)
|
return this.setVideoImportsEnabled(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableImports () {
|
enableVideoImports () {
|
||||||
return this.setImportsEnabled(true)
|
return this.setVideoImportsEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setImportsEnabled (enabled: boolean) {
|
private setVideoImportsEnabled (enabled: boolean) {
|
||||||
return this.updateExistingSubConfig({
|
return this.updateExistingSubConfig({
|
||||||
newConfig: {
|
newConfig: {
|
||||||
import: {
|
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: {
|
enableLive (options: {
|
||||||
allowReplay?: boolean
|
allowReplay?: boolean
|
||||||
transcoding?: boolean
|
transcoding?: boolean
|
||||||
|
@ -552,6 +620,16 @@ export class ConfigCommand extends AbstractCommand {
|
||||||
videoChannelSynchronization: {
|
videoChannelSynchronization: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
maxPerUser: 10
|
maxPerUser: 10
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
users: {
|
||||||
|
enabled: true,
|
||||||
|
maxUserVideoQuota: 5242881,
|
||||||
|
exportExpiration: 1000 * 3600
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trending: {
|
trending: {
|
||||||
|
|
|
@ -17,12 +17,14 @@ import { SocketIOCommand } from '../socket/index.js'
|
||||||
import {
|
import {
|
||||||
AccountsCommand,
|
AccountsCommand,
|
||||||
BlocklistCommand,
|
BlocklistCommand,
|
||||||
|
UserExportsCommand,
|
||||||
LoginCommand,
|
LoginCommand,
|
||||||
NotificationsCommand,
|
NotificationsCommand,
|
||||||
RegistrationsCommand,
|
RegistrationsCommand,
|
||||||
SubscriptionsCommand,
|
SubscriptionsCommand,
|
||||||
TwoFactorCommand,
|
TwoFactorCommand,
|
||||||
UsersCommand
|
UsersCommand,
|
||||||
|
UserImportsCommand
|
||||||
} from '../users/index.js'
|
} from '../users/index.js'
|
||||||
import {
|
import {
|
||||||
BlacklistCommand,
|
BlacklistCommand,
|
||||||
|
@ -33,7 +35,7 @@ import {
|
||||||
ChaptersCommand,
|
ChaptersCommand,
|
||||||
CommentsCommand,
|
CommentsCommand,
|
||||||
HistoryCommand,
|
HistoryCommand,
|
||||||
ImportsCommand,
|
VideoImportsCommand,
|
||||||
LiveCommand,
|
LiveCommand,
|
||||||
PlaylistsCommand,
|
PlaylistsCommand,
|
||||||
ServicesCommand,
|
ServicesCommand,
|
||||||
|
@ -90,7 +92,6 @@ export class PeerTubeServer {
|
||||||
user?: {
|
user?: {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
email?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel?: VideoChannel
|
channel?: VideoChannel
|
||||||
|
@ -134,7 +135,7 @@ export class PeerTubeServer {
|
||||||
changeOwnership?: ChangeOwnershipCommand
|
changeOwnership?: ChangeOwnershipCommand
|
||||||
playlists?: PlaylistsCommand
|
playlists?: PlaylistsCommand
|
||||||
history?: HistoryCommand
|
history?: HistoryCommand
|
||||||
imports?: ImportsCommand
|
videoImports?: VideoImportsCommand
|
||||||
channelSyncs?: ChannelSyncsCommand
|
channelSyncs?: ChannelSyncsCommand
|
||||||
streamingPlaylists?: StreamingPlaylistsCommand
|
streamingPlaylists?: StreamingPlaylistsCommand
|
||||||
channels?: ChannelsCommand
|
channels?: ChannelsCommand
|
||||||
|
@ -155,6 +156,9 @@ export class PeerTubeServer {
|
||||||
storyboard?: StoryboardCommand
|
storyboard?: StoryboardCommand
|
||||||
chapters?: ChaptersCommand
|
chapters?: ChaptersCommand
|
||||||
|
|
||||||
|
userImports?: UserImportsCommand
|
||||||
|
userExports?: UserExportsCommand
|
||||||
|
|
||||||
runners?: RunnersCommand
|
runners?: RunnersCommand
|
||||||
runnerRegistrationTokens?: RunnerRegistrationTokensCommand
|
runnerRegistrationTokens?: RunnerRegistrationTokensCommand
|
||||||
runnerJobs?: RunnerJobsCommand
|
runnerJobs?: RunnerJobsCommand
|
||||||
|
@ -426,7 +430,7 @@ export class PeerTubeServer {
|
||||||
this.changeOwnership = new ChangeOwnershipCommand(this)
|
this.changeOwnership = new ChangeOwnershipCommand(this)
|
||||||
this.playlists = new PlaylistsCommand(this)
|
this.playlists = new PlaylistsCommand(this)
|
||||||
this.history = new HistoryCommand(this)
|
this.history = new HistoryCommand(this)
|
||||||
this.imports = new ImportsCommand(this)
|
this.videoImports = new VideoImportsCommand(this)
|
||||||
this.channelSyncs = new ChannelSyncsCommand(this)
|
this.channelSyncs = new ChannelSyncsCommand(this)
|
||||||
this.streamingPlaylists = new StreamingPlaylistsCommand(this)
|
this.streamingPlaylists = new StreamingPlaylistsCommand(this)
|
||||||
this.channels = new ChannelsCommand(this)
|
this.channels = new ChannelsCommand(this)
|
||||||
|
@ -446,6 +450,9 @@ export class PeerTubeServer {
|
||||||
this.storyboard = new StoryboardCommand(this)
|
this.storyboard = new StoryboardCommand(this)
|
||||||
this.chapters = new ChaptersCommand(this)
|
this.chapters = new ChaptersCommand(this)
|
||||||
|
|
||||||
|
this.userExports = new UserExportsCommand(this)
|
||||||
|
this.userImports = new UserImportsCommand(this)
|
||||||
|
|
||||||
this.runners = new RunnersCommand(this)
|
this.runners = new RunnersCommand(this)
|
||||||
this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this)
|
this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this)
|
||||||
this.runnerJobs = new RunnerJobsCommand(this)
|
this.runnerJobs = new RunnerJobsCommand(this)
|
||||||
|
|
|
@ -21,7 +21,7 @@ function createMultipleServers (totalServers: number, configOverride?: object, o
|
||||||
}
|
}
|
||||||
|
|
||||||
function killallServers (servers: PeerTubeServer[]) {
|
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[]) {
|
async function cleanupTests (servers: PeerTubeServer[]) {
|
||||||
|
@ -33,6 +33,8 @@ async function cleanupTests (servers: PeerTubeServer[]) {
|
||||||
|
|
||||||
let p: Promise<any>[] = []
|
let p: Promise<any>[] = []
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
|
if (!server) continue
|
||||||
|
|
||||||
p = p.concat(server.servers.cleanupTests())
|
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 { isAbsolute } from 'path'
|
||||||
import { HttpStatusCodeType } from '@peertube/peertube-models'
|
import { buildAbsoluteFixturePath, getFileSize } from '@peertube/peertube-node-utils'
|
||||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
|
||||||
import {
|
import {
|
||||||
makeDeleteRequest,
|
makeDeleteRequest,
|
||||||
makeGetRequest,
|
makeGetRequest,
|
||||||
|
@ -10,8 +11,12 @@ import {
|
||||||
unwrapBody,
|
unwrapBody,
|
||||||
unwrapText
|
unwrapText
|
||||||
} from '../requests/requests.js'
|
} 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 type { PeerTubeServer } from '../server/server.js'
|
||||||
|
import { createReadStream } from 'fs'
|
||||||
|
|
||||||
export interface OverrideCommandOptions {
|
export interface OverrideCommandOptions {
|
||||||
token?: string
|
token?: string
|
||||||
|
@ -48,7 +53,7 @@ interface InternalDeleteCommandOptions extends InternalCommonCommandOptions {
|
||||||
rawQuery?: string
|
rawQuery?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractCommand {
|
export abstract class AbstractCommand {
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected server: PeerTubeServer
|
protected server: PeerTubeServer
|
||||||
|
@ -218,8 +223,221 @@ abstract class AbstractCommand {
|
||||||
? { 'x-peertube-video-password': videoPassword }
|
? { 'x-peertube-video-password': videoPassword }
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
// ---------------------------------------------------------------------------
|
||||||
AbstractCommand
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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-command.js'
|
||||||
export * from './accounts.js'
|
export * from './accounts.js'
|
||||||
export * from './blocklist-command.js'
|
export * from './blocklist-command.js'
|
||||||
export * from './login.js'
|
|
||||||
export * from './login-command.js'
|
export * from './login-command.js'
|
||||||
|
export * from './login.js'
|
||||||
export * from './notifications-command.js'
|
export * from './notifications-command.js'
|
||||||
export * from './registrations-command.js'
|
export * from './registrations-command.js'
|
||||||
export * from './subscriptions-command.js'
|
export * from './subscriptions-command.js'
|
||||||
export * from './two-factor-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'
|
export * from './users-command.js'
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import { PeerTubeServer } from '../server/server.js'
|
import { PeerTubeServer } from '../server/server.js'
|
||||||
|
|
||||||
function setAccessTokensToServers (servers: PeerTubeServer[]) {
|
export function setAccessTokensToServers (servers: PeerTubeServer[]) {
|
||||||
const tasks: Promise<any>[] = []
|
return Promise.all(
|
||||||
|
servers.map(async server => {
|
||||||
|
const token = await server.login.getAccessToken()
|
||||||
|
|
||||||
for (const server of servers) {
|
server.accessToken = token
|
||||||
const p = server.login.getAccessToken()
|
})
|
||||||
.then(t => { server.accessToken = t })
|
)
|
||||||
tasks.push(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
setAccessTokensToServers
|
|
||||||
}
|
}
|
||||||
|
|
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 './channel-syncs-command.js'
|
||||||
export * from './comments-command.js'
|
export * from './comments-command.js'
|
||||||
export * from './history-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-command.js'
|
||||||
export * from './live.js'
|
export * from './live.js'
|
||||||
export * from './playlists-command.js'
|
export * from './playlists-command.js'
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
VideoPlaylistElementCreate,
|
VideoPlaylistElementCreate,
|
||||||
VideoPlaylistElementCreateResult,
|
VideoPlaylistElementCreateResult,
|
||||||
VideoPlaylistElementUpdate,
|
VideoPlaylistElementUpdate,
|
||||||
|
VideoPlaylistPrivacy,
|
||||||
|
VideoPlaylistPrivacyType,
|
||||||
VideoPlaylistReorder,
|
VideoPlaylistReorder,
|
||||||
VideoPlaylistType_Type,
|
VideoPlaylistType_Type,
|
||||||
VideoPlaylistUpdate
|
VideoPlaylistUpdate
|
||||||
|
@ -156,6 +158,27 @@ export class PlaylistsCommand extends AbstractCommand {
|
||||||
return body.videoPlaylist
|
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 & {
|
update (options: OverrideCommandOptions & {
|
||||||
attributes: VideoPlaylistUpdate
|
attributes: VideoPlaylistUpdate
|
||||||
playlistId: number | string
|
playlistId: number | string
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { HttpStatusCode, ResultList, VideoImport, VideoImportCreate } from '@pee
|
||||||
import { unwrapBody } from '../requests/index.js'
|
import { unwrapBody } from '../requests/index.js'
|
||||||
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
||||||
|
|
||||||
export class ImportsCommand extends AbstractCommand {
|
export class VideoImportsCommand extends AbstractCommand {
|
||||||
|
|
||||||
importVideo (options: OverrideCommandOptions & {
|
importVideo (options: OverrideCommandOptions & {
|
||||||
attributes: (VideoImportCreate | { torrentfile?: string, previewfile?: string, thumbnailfile?: string })
|
attributes: (VideoImportCreate | { torrentfile?: string, previewfile?: string, thumbnailfile?: string })
|
|
@ -1,9 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
|
/* 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 validator from 'validator'
|
||||||
import { getAllPrivacies, omit, pick, wait } from '@peertube/peertube-core-utils'
|
import { getAllPrivacies, omit, pick, wait } from '@peertube/peertube-core-utils'
|
||||||
import {
|
import {
|
||||||
|
@ -429,7 +425,13 @@ export class VideosCommand extends AbstractCommand {
|
||||||
|
|
||||||
const created = mode === 'legacy'
|
const created = mode === 'legacy'
|
||||||
? await this.buildLegacyUpload({ ...options, attributes })
|
? 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
|
// Wait torrent generation
|
||||||
const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 })
|
const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
@ -456,231 +458,26 @@ export class VideosCommand extends AbstractCommand {
|
||||||
|
|
||||||
path,
|
path,
|
||||||
fields: this.buildUploadFields(options.attributes),
|
fields: this.buildUploadFields(options.attributes),
|
||||||
attaches: this.buildUploadAttaches(options.attributes),
|
attaches: this.buildUploadAttaches(options.attributes, true),
|
||||||
implicitToken: true,
|
implicitToken: true,
|
||||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||||
})).then(body => body.video || body as any)
|
})).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 & {
|
quickUpload (options: OverrideCommandOptions & {
|
||||||
name: string
|
name: string
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
privacy?: VideoPrivacyType
|
privacy?: VideoPrivacyType
|
||||||
fixture?: string
|
fixture?: string
|
||||||
videoPasswords?: string[]
|
videoPasswords?: string[]
|
||||||
|
channelId?: number
|
||||||
}) {
|
}) {
|
||||||
const attributes: VideoEdit = { name: options.name }
|
const attributes: VideoEdit = { name: options.name }
|
||||||
if (options.nsfw) attributes.nsfw = options.nsfw
|
if (options.nsfw) attributes.nsfw = options.nsfw
|
||||||
if (options.privacy) attributes.privacy = options.privacy
|
if (options.privacy) attributes.privacy = options.privacy
|
||||||
if (options.fixture) attributes.fixture = options.fixture
|
if (options.fixture) attributes.fixture = options.fixture
|
||||||
if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords
|
if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords
|
||||||
|
if (options.channelId) attributes.channelId = options.channelId
|
||||||
|
|
||||||
return this.upload({ ...options, attributes })
|
return this.upload({ ...options, attributes })
|
||||||
}
|
}
|
||||||
|
@ -713,7 +510,7 @@ export class VideosCommand extends AbstractCommand {
|
||||||
...options,
|
...options,
|
||||||
|
|
||||||
path: '/api/v1/videos/' + options.videoId + '/source/replace-resumable',
|
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' ])
|
return omit(attributes, [ 'fixture', 'thumbnailfile', 'previewfile' ])
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildUploadAttaches (attributes: VideoEdit) {
|
buildUploadAttaches (attributes: VideoEdit, includeFixture: boolean) {
|
||||||
const attaches: { [ name: string ]: string } = {}
|
const attaches: { [ name: string ]: string } = {}
|
||||||
|
|
||||||
for (const key of [ 'thumbnailfile', 'previewfile' ]) {
|
for (const key of [ 'thumbnailfile', 'previewfile' ]) {
|
||||||
if (attributes[key]) attaches[key] = buildAbsoluteFixturePath(attributes[key])
|
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
|
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",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
"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({
|
registerHook({
|
||||||
|
@ -402,7 +412,8 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
|
||||||
'filter:api.video.upload.video-attribute.result',
|
'filter:api.video.upload.video-attribute.result',
|
||||||
'filter:api.video.import-url.video-attribute.result',
|
'filter:api.video.import-url.video-attribute.result',
|
||||||
'filter:api.video.import-torrent.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({
|
registerHook({
|
||||||
target,
|
target,
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('Test videos import in a channel API validator', function () {
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
await setDefaultVideoChannel([ server ])
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
await server.config.enableImports()
|
await server.config.enableVideoImports()
|
||||||
await server.config.enableChannelSync()
|
await server.config.enableChannelSync()
|
||||||
|
|
||||||
const userCreds = {
|
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 () {
|
it('Should fail when HTTP upload is disabled', async function () {
|
||||||
await server.config.disableChannelSync()
|
await server.config.disableChannelSync()
|
||||||
await server.config.disableImports()
|
await server.config.disableVideoImports()
|
||||||
|
|
||||||
await command.importVideos({
|
await command.importVideos({
|
||||||
channelName: server.store.channel.name,
|
channelName: server.store.channel.name,
|
||||||
|
@ -77,7 +77,7 @@ describe('Test videos import in a channel API validator', function () {
|
||||||
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
||||||
})
|
})
|
||||||
|
|
||||||
await server.config.enableImports()
|
await server.config.enableVideoImports()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail when externalChannelUrl is not provided', async function () {
|
it('Should fail when externalChannelUrl is not provided', async function () {
|
||||||
|
|
|
@ -192,6 +192,16 @@ describe('Test config API validators', function () {
|
||||||
videoChannelSynchronization: {
|
videoChannelSynchronization: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
maxPerUser: 10
|
maxPerUser: 10
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
users: {
|
||||||
|
enabled: false,
|
||||||
|
maxUserVideoQuota: 40,
|
||||||
|
exportExpiration: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trending: {
|
trending: {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import './contact-form.js'
|
||||||
import './custom-pages.js'
|
import './custom-pages.js'
|
||||||
import './debug.js'
|
import './debug.js'
|
||||||
import './follows.js'
|
import './follows.js'
|
||||||
|
import './user-export.js'
|
||||||
|
import './user-import.js.js'
|
||||||
import './jobs.js'
|
import './jobs.js'
|
||||||
import './live.js'
|
import './live.js'
|
||||||
import './logs.js'
|
import './logs.js'
|
||||||
|
|
|
@ -75,13 +75,13 @@ describe('Test upload quota', function () {
|
||||||
channelId: server.store.channel.id,
|
channelId: server.store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
privacy: VideoPrivacy.PUBLIC
|
||||||
}
|
}
|
||||||
await server.imports.importVideo({ attributes: { ...baseAttributes, targetUrl: FIXTURE_URLS.goodVideo } })
|
await server.videoImports.importVideo({ attributes: { ...baseAttributes, targetUrl: FIXTURE_URLS.goodVideo } })
|
||||||
await server.imports.importVideo({ attributes: { ...baseAttributes, magnetUri: FIXTURE_URLS.magnet } })
|
await server.videoImports.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, torrentfile: 'video-720p.torrent' as any } })
|
||||||
|
|
||||||
await waitJobs([ server ])
|
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(total).to.equal(3)
|
||||||
|
|
||||||
expect(videoImports).to.have.lengthOf(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 () {
|
async function importVideo () {
|
||||||
const attributes = { channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
|
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
|
return res.id
|
||||||
}
|
}
|
||||||
|
@ -381,23 +381,23 @@ describe('Test video imports API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an invalid import id', async 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.videoImports.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.delete({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an unknown import id', async function () {
|
it('Should fail with an unknown import id', async function () {
|
||||||
await server.imports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
await server.videoImports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||||
await server.imports.delete({ 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 () {
|
it('Should fail without token', async function () {
|
||||||
await server.imports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
await server.videoImports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
await server.imports.delete({ 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 () {
|
it('Should fail with another user token', async function () {
|
||||||
await server.imports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
await server.videoImports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||||
await server.imports.delete({ 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 () {
|
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 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 () {
|
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 () {
|
it('Should fail to delete a pending import', async function () {
|
||||||
|
@ -417,13 +417,13 @@ describe('Test video imports API validator', function () {
|
||||||
|
|
||||||
importId = await importVideo()
|
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 () {
|
it('Should succeed to cancel an import', async function () {
|
||||||
importId = await importVideo()
|
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') {
|
if (mode === 'import') {
|
||||||
const attributes = { ...baseCorrectParams, targetUrl: FIXTURE_URLS.goodVideo, videoPasswords }
|
const attributes = { ...baseCorrectParams, targetUrl: FIXTURE_URLS.goodVideo, videoPasswords }
|
||||||
return server.imports.importVideo({ attributes, expectedStatus })
|
return server.videoImports.importVideo({ attributes, expectedStatus })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'updateVideo') {
|
if (mode === 'updateVideo') {
|
||||||
|
|
|
@ -8,9 +8,7 @@ import {
|
||||||
BlacklistCommand,
|
BlacklistCommand,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createMultipleServers,
|
createMultipleServers,
|
||||||
doubleFollow,
|
doubleFollow, PeerTubeServer,
|
||||||
killallServers,
|
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
setDefaultChannelAvatar,
|
setDefaultChannelAvatar,
|
||||||
waitJobs
|
waitJobs
|
||||||
|
@ -321,18 +319,7 @@ describe('Test video blacklist', function () {
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(20000)
|
this.timeout(20000)
|
||||||
|
|
||||||
await killallServers([ servers[0] ])
|
await servers[0].config.enableAutoBlacklist()
|
||||||
|
|
||||||
const config = {
|
|
||||||
auto_blacklist: {
|
|
||||||
videos: {
|
|
||||||
of_users: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await servers[0].run(config)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const user = { username: 'user_without_flag', password: 'password' }
|
const user = { username: 'user_without_flag', password: 'password' }
|
||||||
|
@ -380,7 +367,7 @@ describe('Test video blacklist', function () {
|
||||||
name: 'URL import',
|
name: 'URL import',
|
||||||
channelId: channelOfUserWithoutFlag
|
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 })
|
const body = await command.list({ sort: 'createdAt', type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
|
||||||
expect(body.total).to.equal(2)
|
expect(body.total).to.equal(2)
|
||||||
|
@ -393,7 +380,7 @@ describe('Test video blacklist', function () {
|
||||||
name: 'Torrent import',
|
name: 'Torrent import',
|
||||||
channelId: channelOfUserWithoutFlag
|
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 })
|
const body = await command.list({ sort: 'createdAt', type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED })
|
||||||
expect(body.total).to.equal(3)
|
expect(body.total).to.equal(3)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import { wait } from '@peertube/peertube-core-utils'
|
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 { buildUUID } from '@peertube/peertube-node-utils'
|
||||||
import { cleanupTests, PeerTubeServer, waitJobs } from '@peertube/peertube-server-commands'
|
import { cleanupTests, PeerTubeServer, waitJobs } from '@peertube/peertube-server-commands'
|
||||||
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
|
import { MockSmtpServer } from '@tests/shared/mock-servers/mock-email.js'
|
||||||
|
@ -425,7 +425,6 @@ describe('Test moderation notifications', function () {
|
||||||
let uuid: string
|
let uuid: string
|
||||||
let shortUUID: string
|
let shortUUID: string
|
||||||
let videoName: string
|
let videoName: string
|
||||||
let currentCustomConfig: CustomConfig
|
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
|
|
||||||
|
@ -450,23 +449,7 @@ describe('Test moderation notifications', function () {
|
||||||
token: userToken1
|
token: userToken1
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCustomConfig = await servers[0].config.getCustomConfig()
|
await servers[0].config.enableAutoBlacklist()
|
||||||
|
|
||||||
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].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
|
await servers[0].subscriptions.add({ targetUri: 'user_1_channel@' + servers[0].host })
|
||||||
await servers[1].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 () => {
|
after(async () => {
|
||||||
await servers[0].config.updateCustomConfig({ newCustomConfig: currentCustomConfig })
|
|
||||||
|
|
||||||
await servers[0].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
|
await servers[0].subscriptions.remove({ uri: 'user_1_channel@' + servers[0].host })
|
||||||
await servers[1].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,
|
privacy: VideoPrivacy.PUBLIC,
|
||||||
targetUrl: FIXTURE_URLS.goodVideo
|
targetUrl: FIXTURE_URLS.goodVideo
|
||||||
}
|
}
|
||||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
@ -349,7 +349,7 @@ describe('Test user notifications', function () {
|
||||||
targetUrl: FIXTURE_URLS.goodVideo,
|
targetUrl: FIXTURE_URLS.goodVideo,
|
||||||
waitTranscoding: true
|
waitTranscoding: true
|
||||||
}
|
}
|
||||||
const { video } = await servers[1].imports.importVideo({ attributes })
|
const { video } = await servers[1].videoImports.importVideo({ attributes })
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||||
|
@ -524,7 +524,7 @@ describe('Test user notifications', function () {
|
||||||
privacy: VideoPrivacy.PRIVATE,
|
privacy: VideoPrivacy.PRIVATE,
|
||||||
targetUrl: FIXTURE_URLS.badVideo
|
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)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
@ -543,7 +543,7 @@ describe('Test user notifications', function () {
|
||||||
privacy: VideoPrivacy.PRIVATE,
|
privacy: VideoPrivacy.PRIVATE,
|
||||||
targetUrl: FIXTURE_URLS.goodVideo
|
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)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ async function importVideo (server: PeerTubeServer) {
|
||||||
targetUrl: FIXTURE_URLS.goodVideo720
|
targetUrl: FIXTURE_URLS.goodVideo720
|
||||||
}
|
}
|
||||||
|
|
||||||
const { video: { uuid } } = await server.imports.importVideo({ attributes })
|
const { video: { uuid } } = await server.videoImports.importVideo({ attributes })
|
||||||
|
|
||||||
return uuid
|
return uuid
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ describe('Object storage for video import', function () {
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
await setDefaultVideoChannel([ server ])
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
await server.config.enableImports()
|
await server.config.enableVideoImports()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Without transcoding', async function () {
|
describe('Without transcoding', async function () {
|
||||||
|
|
|
@ -59,7 +59,7 @@ describe('Test config defaults', function () {
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
await server.config.disableTranscoding()
|
await server.config.disableTranscoding()
|
||||||
await server.config.enableImports()
|
await server.config.enableVideoImports()
|
||||||
await server.config.enableLive({ allowReplay: false, transcoding: false })
|
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 () {
|
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: {
|
||||||
...attributes,
|
...attributes,
|
||||||
channelId,
|
channelId,
|
||||||
|
@ -95,7 +95,7 @@ describe('Test config defaults', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should respect default values when importing a video using magnet URI', async 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: {
|
||||||
...attributes,
|
...attributes,
|
||||||
channelId,
|
channelId,
|
||||||
|
|
|
@ -112,6 +112,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
||||||
expect(data.import.videos.concurrency).to.equal(2)
|
expect(data.import.videos.concurrency).to.equal(2)
|
||||||
expect(data.import.videos.http.enabled).to.be.true
|
expect(data.import.videos.http.enabled).to.be.true
|
||||||
expect(data.import.videos.torrent.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.autoBlacklist.videos.ofUsers.enabled).to.be.false
|
||||||
|
|
||||||
expect(data.followers.instance.enabled).to.be.true
|
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.broadcastMessage.dismissable).to.be.false
|
||||||
|
|
||||||
expect(data.storyboards.enabled).to.be.true
|
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) {
|
function checkUpdatedConfig (data: CustomConfig) {
|
||||||
|
@ -227,6 +233,8 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.import.videos.concurrency).to.equal(4)
|
expect(data.import.videos.concurrency).to.equal(4)
|
||||||
expect(data.import.videos.http.enabled).to.be.false
|
expect(data.import.videos.http.enabled).to.be.false
|
||||||
expect(data.import.videos.torrent.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.autoBlacklist.videos.ofUsers.enabled).to.be.true
|
||||||
|
|
||||||
expect(data.followers.instance.enabled).to.be.false
|
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.broadcastMessage.dismissable).to.be.true
|
||||||
|
|
||||||
expect(data.storyboards.enabled).to.be.false
|
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 = {
|
const newCustomConfig: CustomConfig = {
|
||||||
|
@ -415,6 +427,9 @@ const newCustomConfig: CustomConfig = {
|
||||||
videoChannelSynchronization: {
|
videoChannelSynchronization: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
maxPerUser: 10
|
maxPerUser: 10
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
enabled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trending: {
|
trending: {
|
||||||
|
@ -469,6 +484,13 @@ const newCustomConfig: CustomConfig = {
|
||||||
},
|
},
|
||||||
storyboards: {
|
storyboards: {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
users: {
|
||||||
|
enabled: false,
|
||||||
|
exportExpiration: 43,
|
||||||
|
maxUserVideoQuota: 42
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ describe('Test proxy', function () {
|
||||||
describe('Videos import', async function () {
|
describe('Videos import', async function () {
|
||||||
|
|
||||||
function quickImport (expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
function quickImport (expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
|
||||||
return servers[0].imports.importVideo({
|
return servers[0].videoImports.importVideo({
|
||||||
attributes: {
|
attributes: {
|
||||||
name: 'video import',
|
name: 'video import',
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import './oauth.js'
|
import './oauth.js'
|
||||||
import './registrations`.js'
|
import './registrations`.js'
|
||||||
import './two-factor.js'
|
import './two-factor.js'
|
||||||
|
import './user-export.js'
|
||||||
|
import './user-import.js'
|
||||||
import './user-subscriptions.js'
|
import './user-subscriptions.js'
|
||||||
import './user-videos.js'
|
import './user-videos.js'
|
||||||
import './users.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 () {
|
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(total).to.equal(2)
|
||||||
expect(data).to.have.lengthOf(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 () {
|
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(total).to.equal(4)
|
||||||
expect(data).to.have.lengthOf(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 () {
|
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(total).to.equal(2)
|
||||||
expect(data).to.have.lengthOf(2)
|
expect(data).to.have.lengthOf(2)
|
||||||
|
|
|
@ -42,16 +42,22 @@ describe('Test resumable upload', function () {
|
||||||
|
|
||||||
const size = await buildSize(defaultFixture, options.size)
|
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 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]
|
return res.header['location'].split('?')[1]
|
||||||
}
|
}
|
||||||
|
@ -71,7 +77,7 @@ describe('Test resumable upload', function () {
|
||||||
const size = await buildSize(defaultFixture, options.size)
|
const size = await buildSize(defaultFixture, options.size)
|
||||||
const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
|
const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
|
||||||
|
|
||||||
return server.videos.sendResumableChunks({
|
return server.videos.sendResumableVideoChunks({
|
||||||
token,
|
token,
|
||||||
path,
|
path,
|
||||||
pathUploadId,
|
pathUploadId,
|
||||||
|
@ -133,7 +139,7 @@ describe('Test resumable upload', function () {
|
||||||
it('Should correctly delete files after an upload', async function () {
|
it('Should correctly delete files after an upload', async function () {
|
||||||
const uploadId = await prepareUpload()
|
const uploadId = await prepareUpload()
|
||||||
await sendChunks({ pathUploadId: uploadId })
|
await sendChunks({ pathUploadId: uploadId })
|
||||||
await server.videos.endResumableUpload({ path, pathUploadId: uploadId })
|
await server.videos.endVideoResumableUpload({ path, pathUploadId: uploadId })
|
||||||
|
|
||||||
expect(await countResumableUploads()).to.equal(0)
|
expect(await countResumableUploads()).to.equal(0)
|
||||||
})
|
})
|
||||||
|
|
|
@ -92,7 +92,7 @@ describe('Test channel synchronizations', function () {
|
||||||
this.timeout(120_000)
|
this.timeout(120_000)
|
||||||
|
|
||||||
{
|
{
|
||||||
const { video } = await servers[0].imports.importVideo({
|
const { video } = await servers[0].videoImports.importVideo({
|
||||||
attributes: {
|
attributes: {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC,
|
privacy: VideoPrivacy.PUBLIC,
|
||||||
|
@ -210,7 +210,7 @@ describe('Test channel synchronizations', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should list imports of a channel synchronization', async 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(total).to.equal(1)
|
||||||
expect(data).to.have.lengthOf(1)
|
expect(data).to.have.lengthOf(1)
|
||||||
|
|
|
@ -237,7 +237,7 @@ describe('Test video chapters', function () {
|
||||||
targetUrl: FIXTURE_URLS.youtubeChapters,
|
targetUrl: FIXTURE_URLS.youtubeChapters,
|
||||||
description: 'this is a super description\n'
|
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)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ describe('Test video chapters', function () {
|
||||||
'00:03 chapter 2\n' +
|
'00:03 chapter 2\n' +
|
||||||
'00:04 chapter 3\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)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
@ -309,7 +309,7 @@ describe('Test video chapters', function () {
|
||||||
privacy: VideoPrivacy.PUBLIC,
|
privacy: VideoPrivacy.PUBLIC,
|
||||||
targetUrl: FIXTURE_URLS.chatersVideo
|
targetUrl: FIXTURE_URLS.chatersVideo
|
||||||
}
|
}
|
||||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ describe('Test video imports', function () {
|
||||||
|
|
||||||
{
|
{
|
||||||
const attributes = { ...baseAttributes, targetUrl: FIXTURE_URLS.youtube }
|
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')
|
expect(video.name).to.equal('small video - youtube')
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -174,7 +174,7 @@ describe('Test video imports', function () {
|
||||||
description: 'this is a super torrent description',
|
description: 'this is a super torrent description',
|
||||||
tags: [ 'tag_torrent1', 'tag_torrent2' ]
|
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')
|
expect(video.name).to.equal('super peertube2 video')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ describe('Test video imports', function () {
|
||||||
description: 'this is a super torrent description',
|
description: 'this is a super torrent description',
|
||||||
tags: [ 'tag_torrent1', 'tag_torrent2' ]
|
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')
|
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 () {
|
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(total).to.equal(3)
|
||||||
|
|
||||||
expect(videoImports).to.have.lengthOf(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 () {
|
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(total).to.equal(1)
|
||||||
expect(videoImports).to.have.lengthOf(1)
|
expect(videoImports).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ describe('Test video imports', function () {
|
||||||
|
|
||||||
it('Should search in my imports', async 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(total).to.equal(1)
|
||||||
expect(data).to.have.lengthOf(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(total).to.equal(1)
|
||||||
expect(data).to.have.lengthOf(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 () {
|
it('Should import a video on server 2 with some fields', async function () {
|
||||||
this.timeout(60_000)
|
this.timeout(60_000)
|
||||||
|
|
||||||
const { video } = await servers[1].imports.importVideo({
|
const { video } = await servers[1].videoImports.importVideo({
|
||||||
attributes: {
|
attributes: {
|
||||||
targetUrl: FIXTURE_URLS.youtube,
|
targetUrl: FIXTURE_URLS.youtube,
|
||||||
channelId: servers[1].store.channel.id,
|
channelId: servers[1].store.channel.id,
|
||||||
|
@ -312,7 +312,7 @@ describe('Test video imports', function () {
|
||||||
channelId: servers[1].store.channel.id,
|
channelId: servers[1].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
privacy: VideoPrivacy.PUBLIC
|
||||||
}
|
}
|
||||||
const { video } = await servers[1].imports.importVideo({ attributes })
|
const { video } = await servers[1].videoImports.importVideo({ attributes })
|
||||||
const videoUUID = video.uuid
|
const videoUUID = video.uuid
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -354,7 +354,7 @@ describe('Test video imports', function () {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
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
|
const videoUUID = videoImported.uuid
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -394,7 +394,7 @@ describe('Test video imports', function () {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
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
|
const videoUUID = videoImported.uuid
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -422,7 +422,7 @@ describe('Test video imports', function () {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
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
|
const videoUUID = videoImported.uuid
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -454,7 +454,7 @@ describe('Test video imports', function () {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
privacy: VideoPrivacy.PUBLIC
|
||||||
}
|
}
|
||||||
const { video } = await servers[0].imports.importVideo({ attributes })
|
const { video } = await servers[0].videoImports.importVideo({ attributes })
|
||||||
const videoUUID = video.uuid
|
const videoUUID = video.uuid
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -497,7 +497,7 @@ describe('Test video imports', function () {
|
||||||
|
|
||||||
async function importVideo (name: string) {
|
async function importVideo (name: string) {
|
||||||
const attributes = { name, channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
|
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
|
return res.id
|
||||||
}
|
}
|
||||||
|
@ -516,16 +516,16 @@ describe('Test video imports', function () {
|
||||||
await server.jobs.pauseJobQueue()
|
await server.jobs.pauseJobQueue()
|
||||||
pendingImportId = await importVideo('pending')
|
pendingImportId = await importVideo('pending')
|
||||||
|
|
||||||
const { data } = await server.imports.getMyVideoImports()
|
const { data } = await server.videoImports.getMyVideoImports()
|
||||||
expect(data).to.have.lengthOf(2)
|
expect(data).to.have.lengthOf(2)
|
||||||
|
|
||||||
finishedVideo = data.find(i => i.id === finishedImportId).video
|
finishedVideo = data.find(i => i.id === finishedImportId).video
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should delete a video import', async function () {
|
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).to.have.lengthOf(1)
|
||||||
expect(data[0].id).to.equal(pendingImportId)
|
expect(data[0].id).to.equal(pendingImportId)
|
||||||
expect(data[0].state.id).to.equal(VideoImportState.PENDING)
|
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 () {
|
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).to.have.lengthOf(1)
|
||||||
expect(data[0].id).to.equal(pendingImportId)
|
expect(data[0].id).to.equal(pendingImportId)
|
||||||
expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
|
expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
|
||||||
|
@ -553,7 +553,7 @@ describe('Test video imports', function () {
|
||||||
|
|
||||||
await waitJobs([ server ])
|
await waitJobs([ server ])
|
||||||
|
|
||||||
const { data } = await server.imports.getMyVideoImports()
|
const { data } = await server.videoImports.getMyVideoImports()
|
||||||
expect(data).to.have.lengthOf(1)
|
expect(data).to.have.lengthOf(1)
|
||||||
expect(data[0].id).to.equal(pendingImportId)
|
expect(data[0].id).to.equal(pendingImportId)
|
||||||
expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
|
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 () {
|
it('Should delete the cancelled video import', async function () {
|
||||||
await server.imports.delete({ importId: pendingImportId })
|
await server.videoImports.delete({ importId: pendingImportId })
|
||||||
const { data } = await server.imports.getMyVideoImports()
|
const { data } = await server.videoImports.getMyVideoImports()
|
||||||
expect(data).to.have.lengthOf(0)
|
expect(data).to.have.lengthOf(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -581,7 +581,7 @@ describe('Test video imports', function () {
|
||||||
privacy: VideoPrivacy.PUBLIC
|
privacy: VideoPrivacy.PUBLIC
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.imports.importVideo({ attributes })
|
return server.videoImports.importVideo({ attributes })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testBinaryUpdate (releaseUrl: string, releaseName: string) {
|
async function testBinaryUpdate (releaseUrl: string, releaseName: string) {
|
||||||
|
|
|
@ -263,20 +263,6 @@ describe('Test a video file replacement', function () {
|
||||||
|
|
||||||
describe('Autoblacklist', 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) {
|
async function expectBlacklist (uuid: string, value: boolean) {
|
||||||
const video = await servers[0].videos.getWithToken({ id: uuid })
|
const video = await servers[0].videos.getWithToken({ id: uuid })
|
||||||
|
|
||||||
|
@ -284,7 +270,7 @@ describe('Test a video file replacement', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
await updateAutoBlacklist(true)
|
await servers[0].config.enableAutoBlacklist()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should auto blacklist an unblacklisted video after file replacement', async function () {
|
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 servers[0].blacklist.remove({ videoId: uuid })
|
||||||
await expectBlacklist(uuid, false)
|
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 servers[0].videos.replaceSourceFile({ videoId: uuid, token: userToken, fixture: 'video_short1.webm' })
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
|
@ -126,7 +126,7 @@ describe('Test video storyboard', function () {
|
||||||
if (areHttpImportTestsDisabled()) return
|
if (areHttpImportTestsDisabled()) return
|
||||||
|
|
||||||
// 3s video
|
// 3s video
|
||||||
const { video } = await servers[0].imports.importVideo({
|
const { video } = await servers[0].videoImports.importVideo({
|
||||||
attributes: {
|
attributes: {
|
||||||
targetUrl: FIXTURE_URLS.goodVideo,
|
targetUrl: FIXTURE_URLS.goodVideo,
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
|
@ -146,7 +146,7 @@ describe('Test video storyboard', function () {
|
||||||
if (areHttpImportTestsDisabled()) return
|
if (areHttpImportTestsDisabled()) return
|
||||||
|
|
||||||
// 10s video
|
// 10s video
|
||||||
const { video } = await servers[0].imports.importVideo({
|
const { video } = await servers[0].videoImports.importVideo({
|
||||||
attributes: {
|
attributes: {
|
||||||
magnetUri: FIXTURE_URLS.magnet,
|
magnetUri: FIXTURE_URLS.magnet,
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
|
|
|
@ -24,12 +24,14 @@ import {
|
||||||
waitJobs
|
waitJobs
|
||||||
} from '@peertube/peertube-server-commands'
|
} from '@peertube/peertube-server-commands'
|
||||||
import { FIXTURE_URLS } from '../shared/tests.js'
|
import { FIXTURE_URLS } from '../shared/tests.js'
|
||||||
|
import { expectEndWith } from '@tests/shared/checks.js'
|
||||||
|
|
||||||
describe('Test plugin filter hooks', function () {
|
describe('Test plugin filter hooks', function () {
|
||||||
let servers: PeerTubeServer[]
|
let servers: PeerTubeServer[]
|
||||||
let videoUUID: string
|
let videoUUID: string
|
||||||
let threadId: number
|
let threadId: number
|
||||||
let videoPlaylistUUID: string
|
let videoPlaylistUUID: string
|
||||||
|
let importUserToken: string
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
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
|
// Root subscribes to itself
|
||||||
await servers[0].subscriptions.add({ targetUri: 'root_channel@' + servers[0].host })
|
await servers[0].subscriptions.add({ targetUri: 'root_channel@' + servers[0].host })
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Videos', function () {
|
describe('Videos', function () {
|
||||||
|
@ -95,7 +107,7 @@ describe('Test plugin filter hooks', function () {
|
||||||
const { total } = await servers[0].videos.list({ start: 0, count: 0 })
|
const { total } = await servers[0].videos.list({ start: 0, count: 0 })
|
||||||
|
|
||||||
// Plugin do +1 to the total result
|
// 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 () {
|
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,
|
channelId: servers[0].store.channel.id,
|
||||||
targetUrl: FIXTURE_URLS.goodVideo + 'bad'
|
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 () {
|
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,
|
channelId: servers[0].store.channel.id,
|
||||||
torrentfile: 'video-720p.torrent' as any
|
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 () {
|
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,
|
channelId: servers[0].store.channel.id,
|
||||||
targetUrl: FIXTURE_URLS.goodVideo
|
targetUrl: FIXTURE_URLS.goodVideo
|
||||||
}
|
}
|
||||||
const body = await servers[0].imports.importVideo({ attributes })
|
const body = await servers[0].videoImports.importVideo({ attributes })
|
||||||
videoImportId = body.id
|
videoImportId = body.id
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
{
|
{
|
||||||
const body = await servers[0].imports.getMyVideoImports()
|
const body = await servers[0].videoImports.getMyVideoImports()
|
||||||
const videoImports = body.data
|
const videoImports = body.data
|
||||||
|
|
||||||
const videoImport = videoImports.find(i => i.id === videoImportId)
|
const videoImport = videoImports.find(i => i.id === videoImportId)
|
||||||
|
@ -269,14 +281,14 @@ describe('Test plugin filter hooks', function () {
|
||||||
channelId: servers[0].store.channel.id,
|
channelId: servers[0].store.channel.id,
|
||||||
torrentfile: 'video-720p.torrent' as any
|
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
|
videoImportId = body.id
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitJobs(servers)
|
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)
|
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')
|
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 () {
|
describe('Video comments accept', function () {
|
||||||
|
@ -413,7 +433,7 @@ describe('Test plugin filter hooks', function () {
|
||||||
targetUrl: FIXTURE_URLS.goodVideo,
|
targetUrl: FIXTURE_URLS.goodVideo,
|
||||||
channelId: servers[0].store.channel.id
|
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)
|
await checkIsBlacklisted(body.video.uuid, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -739,7 +759,7 @@ describe('Test plugin filter hooks', function () {
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
await servers[0].config.enableLive({ transcoding: false, allowReplay: false })
|
await servers[0].config.enableLive({ transcoding: false, allowReplay: false })
|
||||||
await servers[0].config.enableImports()
|
await servers[0].config.enableVideoImports()
|
||||||
await servers[0].config.disableTranscoding()
|
await servers[0].config.disableTranscoding()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -760,7 +780,7 @@ describe('Test plugin filter hooks', function () {
|
||||||
targetUrl: FIXTURE_URLS.goodVideo,
|
targetUrl: FIXTURE_URLS.goodVideo,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
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 })
|
const video = await servers[0].videos.get({ id })
|
||||||
expect(video.description).to.equal('import url - filter:api.video.import-url.video-attribute.result')
|
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,
|
magnetUri: FIXTURE_URLS.magnet,
|
||||||
privacy: VideoPrivacy.PUBLIC
|
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 })
|
const video = await servers[0].videos.get({ id })
|
||||||
expect(video.description).to.equal('import torrent - filter:api.video.import-torrent.video-attribute.result')
|
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 })
|
const video = await servers[0].videos.get({ id })
|
||||||
expect(video.description).to.equal('live - filter:api.video.live.video-attribute.result')
|
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 () {
|
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 })
|
const { total } = await servers[0].channels.list({ start: 0, count: 1 })
|
||||||
|
|
||||||
// plugin do +1 to the total parameter
|
// 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 () {
|
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
|
server: PeerTubeServer
|
||||||
originServer: PeerTubeServer
|
originServer: PeerTubeServer
|
||||||
videoUUID: string
|
videoUUID: string
|
||||||
|
objectStorageBaseUrl?: string
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
name: string
|
name: string
|
||||||
category: number
|
category: number
|
||||||
|
@ -150,7 +152,7 @@ async function completeVideoCheck (options: {
|
||||||
previewfile?: string
|
previewfile?: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const { attributes, originServer, server, videoUUID } = options
|
const { attributes, originServer, server, videoUUID, objectStorageBaseUrl } = options
|
||||||
|
|
||||||
await loadLanguages()
|
await loadLanguages()
|
||||||
|
|
||||||
|
@ -215,7 +217,13 @@ async function completeVideoCheck (options: {
|
||||||
await testImageGeneratedByFFmpeg(server.url, attributes.previewfile, video.previewPath)
|
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: {
|
async function checkVideoFilesWereRemoved (options: {
|
||||||
|
@ -290,9 +298,11 @@ function checkUploadVideoParam (options: {
|
||||||
|
|
||||||
return mode === 'legacy'
|
return mode === 'legacy'
|
||||||
? server.videos.buildLegacyUpload({ token, attributes, expectedStatus: expectedStatus || completedExpectedStatus })
|
? server.videos.buildLegacyUpload({ token, attributes, expectedStatus: expectedStatus || completedExpectedStatus })
|
||||||
: server.videos.buildResumeUpload({
|
: server.videos.buildResumeVideoUpload({
|
||||||
token,
|
token,
|
||||||
attributes,
|
fixture: attributes.fixture,
|
||||||
|
attaches: this.buildUploadAttaches(attributes),
|
||||||
|
fields: this.buildUploadFields(attributes),
|
||||||
expectedStatus,
|
expectedStatus,
|
||||||
completedExpectedStatus,
|
completedExpectedStatus,
|
||||||
path: '/api/v1/videos/upload-resumable'
|
path: '/api/v1/videos/upload-resumable'
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@tests/*": [ "src/*" ],
|
"@tests/*": [ "./src/*" ],
|
||||||
"@server/*": [ "../../server/core/*" ]
|
"@server/*": [ "../../server/core/*" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue