From 94565d52bb2883e09f16d1363170ac9c0dccb7a1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Apr 2019 15:26:15 +0200 Subject: Shared utils -> extra-utils Because they need dev dependencies --- shared/extra-utils/cli/cli.ts | 24 + shared/extra-utils/feeds/feeds.ts | 32 + shared/extra-utils/index.ts | 26 + shared/extra-utils/logs/logs.ts | 18 + shared/extra-utils/miscs/email-child-process.js | 27 + shared/extra-utils/miscs/email.ts | 64 ++ shared/extra-utils/miscs/miscs.ts | 101 ++++ shared/extra-utils/miscs/sql.ts | 80 +++ shared/extra-utils/miscs/stubs.ts | 14 + shared/extra-utils/overviews/overviews.ts | 18 + shared/extra-utils/requests/activitypub.ts | 43 ++ shared/extra-utils/requests/check-api-params.ts | 40 ++ shared/extra-utils/requests/requests.ts | 184 ++++++ shared/extra-utils/search/video-channels.ts | 22 + shared/extra-utils/search/videos.ts | 77 +++ shared/extra-utils/server/activitypub.ts | 14 + shared/extra-utils/server/clients.ts | 19 + shared/extra-utils/server/config.ts | 156 +++++ shared/extra-utils/server/contact-form.ts | 28 + shared/extra-utils/server/follows.ts | 111 ++++ shared/extra-utils/server/jobs.ts | 82 +++ shared/extra-utils/server/redundancy.ts | 17 + shared/extra-utils/server/servers.ts | 219 +++++++ shared/extra-utils/server/stats.ts | 22 + shared/extra-utils/socket/socket-io.ts | 13 + shared/extra-utils/users/accounts.ts | 80 +++ shared/extra-utils/users/blocklist.ts | 197 +++++++ shared/extra-utils/users/login.ts | 62 ++ shared/extra-utils/users/user-notifications.ts | 496 ++++++++++++++++ shared/extra-utils/users/user-subscriptions.ts | 82 +++ shared/extra-utils/users/users.ts | 330 +++++++++++ shared/extra-utils/videos/services.ts | 23 + shared/extra-utils/videos/video-abuses.ts | 65 +++ shared/extra-utils/videos/video-blacklist.ts | 72 +++ shared/extra-utils/videos/video-captions.ts | 71 +++ .../extra-utils/videos/video-change-ownership.ts | 54 ++ shared/extra-utils/videos/video-channels.ts | 134 +++++ shared/extra-utils/videos/video-comments.ts | 87 +++ shared/extra-utils/videos/video-history.ts | 39 ++ shared/extra-utils/videos/video-imports.ts | 57 ++ shared/extra-utils/videos/video-playlists.ts | 318 ++++++++++ .../videos/video-streaming-playlists.ts | 51 ++ shared/extra-utils/videos/videos.ts | 648 +++++++++++++++++++++ shared/utils/cli/cli.ts | 24 - shared/utils/feeds/feeds.ts | 32 - shared/utils/index.ts | 26 - shared/utils/logs/logs.ts | 18 - shared/utils/miscs/email-child-process.js | 27 - shared/utils/miscs/email.ts | 64 -- shared/utils/miscs/miscs.ts | 101 ---- shared/utils/miscs/sql.ts | 80 --- shared/utils/miscs/stubs.ts | 14 - shared/utils/overviews/overviews.ts | 18 - shared/utils/requests/activitypub.ts | 43 -- shared/utils/requests/check-api-params.ts | 40 -- shared/utils/requests/requests.ts | 184 ------ shared/utils/search/video-channels.ts | 22 - shared/utils/search/videos.ts | 77 --- shared/utils/server/activitypub.ts | 14 - shared/utils/server/clients.ts | 19 - shared/utils/server/config.ts | 156 ----- shared/utils/server/contact-form.ts | 28 - shared/utils/server/follows.ts | 111 ---- shared/utils/server/jobs.ts | 82 --- shared/utils/server/redundancy.ts | 17 - shared/utils/server/servers.ts | 219 ------- shared/utils/server/stats.ts | 22 - shared/utils/socket/socket-io.ts | 13 - shared/utils/users/accounts.ts | 80 --- shared/utils/users/blocklist.ts | 197 ------- shared/utils/users/login.ts | 62 -- shared/utils/users/user-notifications.ts | 496 ---------------- shared/utils/users/user-subscriptions.ts | 82 --- shared/utils/users/users.ts | 330 ----------- shared/utils/videos/services.ts | 23 - shared/utils/videos/video-abuses.ts | 65 --- shared/utils/videos/video-blacklist.ts | 72 --- shared/utils/videos/video-captions.ts | 71 --- shared/utils/videos/video-change-ownership.ts | 54 -- shared/utils/videos/video-channels.ts | 134 ----- shared/utils/videos/video-comments.ts | 87 --- shared/utils/videos/video-history.ts | 39 -- shared/utils/videos/video-imports.ts | 57 -- shared/utils/videos/video-playlists.ts | 318 ---------- shared/utils/videos/video-streaming-playlists.ts | 51 -- shared/utils/videos/videos.ts | 648 --------------------- 86 files changed, 4317 insertions(+), 4317 deletions(-) create mode 100644 shared/extra-utils/cli/cli.ts create mode 100644 shared/extra-utils/feeds/feeds.ts create mode 100644 shared/extra-utils/index.ts create mode 100644 shared/extra-utils/logs/logs.ts create mode 100644 shared/extra-utils/miscs/email-child-process.js create mode 100644 shared/extra-utils/miscs/email.ts create mode 100644 shared/extra-utils/miscs/miscs.ts create mode 100644 shared/extra-utils/miscs/sql.ts create mode 100644 shared/extra-utils/miscs/stubs.ts create mode 100644 shared/extra-utils/overviews/overviews.ts create mode 100644 shared/extra-utils/requests/activitypub.ts create mode 100644 shared/extra-utils/requests/check-api-params.ts create mode 100644 shared/extra-utils/requests/requests.ts create mode 100644 shared/extra-utils/search/video-channels.ts create mode 100644 shared/extra-utils/search/videos.ts create mode 100644 shared/extra-utils/server/activitypub.ts create mode 100644 shared/extra-utils/server/clients.ts create mode 100644 shared/extra-utils/server/config.ts create mode 100644 shared/extra-utils/server/contact-form.ts create mode 100644 shared/extra-utils/server/follows.ts create mode 100644 shared/extra-utils/server/jobs.ts create mode 100644 shared/extra-utils/server/redundancy.ts create mode 100644 shared/extra-utils/server/servers.ts create mode 100644 shared/extra-utils/server/stats.ts create mode 100644 shared/extra-utils/socket/socket-io.ts create mode 100644 shared/extra-utils/users/accounts.ts create mode 100644 shared/extra-utils/users/blocklist.ts create mode 100644 shared/extra-utils/users/login.ts create mode 100644 shared/extra-utils/users/user-notifications.ts create mode 100644 shared/extra-utils/users/user-subscriptions.ts create mode 100644 shared/extra-utils/users/users.ts create mode 100644 shared/extra-utils/videos/services.ts create mode 100644 shared/extra-utils/videos/video-abuses.ts create mode 100644 shared/extra-utils/videos/video-blacklist.ts create mode 100644 shared/extra-utils/videos/video-captions.ts create mode 100644 shared/extra-utils/videos/video-change-ownership.ts create mode 100644 shared/extra-utils/videos/video-channels.ts create mode 100644 shared/extra-utils/videos/video-comments.ts create mode 100644 shared/extra-utils/videos/video-history.ts create mode 100644 shared/extra-utils/videos/video-imports.ts create mode 100644 shared/extra-utils/videos/video-playlists.ts create mode 100644 shared/extra-utils/videos/video-streaming-playlists.ts create mode 100644 shared/extra-utils/videos/videos.ts delete mode 100644 shared/utils/cli/cli.ts delete mode 100644 shared/utils/feeds/feeds.ts delete mode 100644 shared/utils/index.ts delete mode 100644 shared/utils/logs/logs.ts delete mode 100644 shared/utils/miscs/email-child-process.js delete mode 100644 shared/utils/miscs/email.ts delete mode 100644 shared/utils/miscs/miscs.ts delete mode 100644 shared/utils/miscs/sql.ts delete mode 100644 shared/utils/miscs/stubs.ts delete mode 100644 shared/utils/overviews/overviews.ts delete mode 100644 shared/utils/requests/activitypub.ts delete mode 100644 shared/utils/requests/check-api-params.ts delete mode 100644 shared/utils/requests/requests.ts delete mode 100644 shared/utils/search/video-channels.ts delete mode 100644 shared/utils/search/videos.ts delete mode 100644 shared/utils/server/activitypub.ts delete mode 100644 shared/utils/server/clients.ts delete mode 100644 shared/utils/server/config.ts delete mode 100644 shared/utils/server/contact-form.ts delete mode 100644 shared/utils/server/follows.ts delete mode 100644 shared/utils/server/jobs.ts delete mode 100644 shared/utils/server/redundancy.ts delete mode 100644 shared/utils/server/servers.ts delete mode 100644 shared/utils/server/stats.ts delete mode 100644 shared/utils/socket/socket-io.ts delete mode 100644 shared/utils/users/accounts.ts delete mode 100644 shared/utils/users/blocklist.ts delete mode 100644 shared/utils/users/login.ts delete mode 100644 shared/utils/users/user-notifications.ts delete mode 100644 shared/utils/users/user-subscriptions.ts delete mode 100644 shared/utils/users/users.ts delete mode 100644 shared/utils/videos/services.ts delete mode 100644 shared/utils/videos/video-abuses.ts delete mode 100644 shared/utils/videos/video-blacklist.ts delete mode 100644 shared/utils/videos/video-captions.ts delete mode 100644 shared/utils/videos/video-change-ownership.ts delete mode 100644 shared/utils/videos/video-channels.ts delete mode 100644 shared/utils/videos/video-comments.ts delete mode 100644 shared/utils/videos/video-history.ts delete mode 100644 shared/utils/videos/video-imports.ts delete mode 100644 shared/utils/videos/video-playlists.ts delete mode 100644 shared/utils/videos/video-streaming-playlists.ts delete mode 100644 shared/utils/videos/videos.ts (limited to 'shared') diff --git a/shared/extra-utils/cli/cli.ts b/shared/extra-utils/cli/cli.ts new file mode 100644 index 000000000..54d05e9c6 --- /dev/null +++ b/shared/extra-utils/cli/cli.ts @@ -0,0 +1,24 @@ +import { exec } from 'child_process' + +import { ServerInfo } from '../server/servers' + +function getEnvCli (server?: ServerInfo) { + return `NODE_ENV=test NODE_APP_INSTANCE=${server.serverNumber}` +} + +async function execCLI (command: string) { + return new Promise((res, rej) => { + exec(command, (err, stdout, stderr) => { + if (err) return rej(err) + + return res(stdout) + }) + }) +} + +// --------------------------------------------------------------------------- + +export { + execCLI, + getEnvCli +} diff --git a/shared/extra-utils/feeds/feeds.ts b/shared/extra-utils/feeds/feeds.ts new file mode 100644 index 000000000..af6df2b20 --- /dev/null +++ b/shared/extra-utils/feeds/feeds.ts @@ -0,0 +1,32 @@ +import * as request from 'supertest' + +type FeedType = 'videos' | 'video-comments' + +function getXMLfeed (url: string, feed: FeedType, format?: string) { + const path = '/feeds/' + feed + '.xml' + + return request(url) + .get(path) + .query((format) ? { format: format } : {}) + .set('Accept', 'application/xml') + .expect(200) + .expect('Content-Type', /xml/) +} + +function getJSONfeed (url: string, feed: FeedType, query: any = {}) { + const path = '/feeds/' + feed + '.json' + + return request(url) + .get(path) + .query(query) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +// --------------------------------------------------------------------------- + +export { + getXMLfeed, + getJSONfeed +} diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts new file mode 100644 index 000000000..9d0bbaa38 --- /dev/null +++ b/shared/extra-utils/index.ts @@ -0,0 +1,26 @@ +export * from './server/activitypub' +export * from './cli/cli' +export * from './server/clients' +export * from './server/config' +export * from './server/jobs' +export * from './users/login' +export * from './miscs/miscs' +export * from './miscs/stubs' +export * from './miscs/sql' +export * from './server/follows' +export * from './requests/requests' +export * from './requests/check-api-params' +export * from './server/servers' +export * from './videos/services' +export * from './videos/video-playlists' +export * from './users/users' +export * from './users/accounts' +export * from './videos/video-abuses' +export * from './videos/video-blacklist' +export * from './videos/video-channels' +export * from './videos/video-comments' +export * from './videos/video-streaming-playlists' +export * from './videos/videos' +export * from './videos/video-change-ownership' +export * from './feeds/feeds' +export * from './search/videos' diff --git a/shared/extra-utils/logs/logs.ts b/shared/extra-utils/logs/logs.ts new file mode 100644 index 000000000..cbb1afb93 --- /dev/null +++ b/shared/extra-utils/logs/logs.ts @@ -0,0 +1,18 @@ +import { makeGetRequest } from '../requests/requests' +import { LogLevel } from '../../models/server/log-level.type' + +function getLogs (url: string, accessToken: string, startDate: Date, endDate?: Date, level?: LogLevel) { + const path = '/api/v1/server/logs' + + return makeGetRequest({ + url, + path, + token: accessToken, + query: { startDate, endDate, level }, + statusCodeExpected: 200 + }) +} + +export { + getLogs +} diff --git a/shared/extra-utils/miscs/email-child-process.js b/shared/extra-utils/miscs/email-child-process.js new file mode 100644 index 000000000..40ae37d70 --- /dev/null +++ b/shared/extra-utils/miscs/email-child-process.js @@ -0,0 +1,27 @@ +const MailDev = require('maildev') + +// must run maildev as forked ChildProcess +// failed instantiation stops main process with exit code 0 +process.on('message', (msg) => { + if (msg.start) { + const maildev = new MailDev({ + ip: '127.0.0.1', + smtp: 1025, + disableWeb: true, + silent: true + }) + + maildev.on('new', email => { + process.send({ email }) + }) + + maildev.listen(err => { + if (err) { + // cannot send as Error object + return process.send({ err: err.message }) + } + + return process.send({ err: null }) + }) + } +}) diff --git a/shared/extra-utils/miscs/email.ts b/shared/extra-utils/miscs/email.ts new file mode 100644 index 000000000..f9f1bd95b --- /dev/null +++ b/shared/extra-utils/miscs/email.ts @@ -0,0 +1,64 @@ +import { fork, ChildProcess } from 'child_process' + +class MockSmtpServer { + + private static instance: MockSmtpServer + private started = false + private emailChildProcess: ChildProcess + private emails: object[] + + private constructor () { + this.emailChildProcess = fork(`${__dirname}/email-child-process`, []) + + this.emailChildProcess.on('message', (msg) => { + if (msg.email) { + return this.emails.push(msg.email) + } + }) + + process.on('exit', () => this.kill()) + } + + collectEmails (emailsCollection: object[]) { + return new Promise((res, rej) => { + if (this.started) { + this.emails = emailsCollection + return res() + } + + // ensure maildev isn't started until + // unexpected exit can be reported to test runner + this.emailChildProcess.send({ start: true }) + this.emailChildProcess.on('exit', () => { + return rej(new Error('maildev exited unexpectedly, confirm port not in use')) + }) + this.emailChildProcess.on('message', (msg) => { + if (msg.err) { + return rej(new Error(msg.err)) + } + this.started = true + this.emails = emailsCollection + return res() + }) + }) + } + + kill () { + if (!this.emailChildProcess) return + + process.kill(this.emailChildProcess.pid) + + this.emailChildProcess = null + MockSmtpServer.instance = null + } + + static get Instance () { + return this.instance || (this.instance = new this()) + } +} + +// --------------------------------------------------------------------------- + +export { + MockSmtpServer +} diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts new file mode 100644 index 000000000..d1ffb7be4 --- /dev/null +++ b/shared/extra-utils/miscs/miscs.ts @@ -0,0 +1,101 @@ +/* tslint:disable:no-unused-expression */ + +import * as chai from 'chai' +import { isAbsolute, join } from 'path' +import * as request from 'supertest' +import * as WebTorrent from 'webtorrent' +import { pathExists, readFile } from 'fs-extra' +import * as ffmpeg from 'fluent-ffmpeg' + +const expect = chai.expect +let webtorrent = new WebTorrent() + +function immutableAssign (target: T, source: U) { + return Object.assign<{}, T, U>({}, target, source) +} + + // Default interval -> 5 minutes +function dateIsValid (dateString: string, interval = 300000) { + const dateToCheck = new Date(dateString) + const now = new Date() + + return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval +} + +function wait (milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)) +} + +function webtorrentAdd (torrent: string, refreshWebTorrent = false) { + if (refreshWebTorrent === true) webtorrent = new WebTorrent() + + return new Promise(res => webtorrent.add(torrent, res)) +} + +function root () { + // We are in /miscs + return join(__dirname, '..', '..', '..') +} + +async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') { + const res = await request(url) + .get(imagePath) + .expect(200) + + const body = res.body + + const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension)) + const minLength = body.length - ((20 * body.length) / 100) + const maxLength = body.length + ((20 * body.length) / 100) + + expect(data.length).to.be.above(minLength) + expect(data.length).to.be.below(maxLength) +} + +function buildAbsoluteFixturePath (path: string, customTravisPath = false) { + if (isAbsolute(path)) { + return path + } + + if (customTravisPath && process.env.TRAVIS) return join(process.env.HOME, 'fixtures', path) + + return join(root(), 'server', 'tests', 'fixtures', path) +} + +async function generateHighBitrateVideo () { + const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true) + + const exists = await pathExists(tempFixturePath) + if (!exists) { + + // Generate a random, high bitrate video on the fly, so we don't have to include + // a large file in the repo. The video needs to have a certain minimum length so + // that FFmpeg properly applies bitrate limits. + // https://stackoverflow.com/a/15795112 + return new Promise(async (res, rej) => { + ffmpeg() + .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) + .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) + .outputOptions([ '-maxrate 10M', '-bufsize 10M' ]) + .output(tempFixturePath) + .on('error', rej) + .on('end', () => res(tempFixturePath)) + .run() + }) + } + + return tempFixturePath +} + +// --------------------------------------------------------------------------- + +export { + dateIsValid, + wait, + webtorrentAdd, + immutableAssign, + testImage, + buildAbsoluteFixturePath, + root, + generateHighBitrateVideo +} diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts new file mode 100644 index 000000000..b281471ce --- /dev/null +++ b/shared/extra-utils/miscs/sql.ts @@ -0,0 +1,80 @@ +import * as Sequelize from 'sequelize' + +let sequelizes: { [ id: number ]: Sequelize.Sequelize } = {} + +function getSequelize (serverNumber: number) { + if (sequelizes[serverNumber]) return sequelizes[serverNumber] + + const dbname = 'peertube_test' + serverNumber + const username = 'peertube' + const password = 'peertube' + const host = 'localhost' + const port = 5432 + + const seq = new Sequelize(dbname, username, password, { + dialect: 'postgres', + host, + port, + operatorsAliases: false, + logging: false + }) + + sequelizes[serverNumber] = seq + + return seq +} + +function setActorField (serverNumber: number, to: string, field: string, value: string) { + const seq = getSequelize(serverNumber) + + const options = { type: Sequelize.QueryTypes.UPDATE } + + return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) +} + +function setVideoField (serverNumber: number, uuid: string, field: string, value: string) { + const seq = getSequelize(serverNumber) + + const options = { type: Sequelize.QueryTypes.UPDATE } + + return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) +} + +function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) { + const seq = getSequelize(serverNumber) + + const options = { type: Sequelize.QueryTypes.UPDATE } + + return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) +} + +async function countVideoViewsOf (serverNumber: number, uuid: string) { + const seq = getSequelize(serverNumber) + + // tslint:disable + const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` + + const options = { type: Sequelize.QueryTypes.SELECT } + const [ { total } ] = await seq.query(query, options) + + if (!total) return 0 + + return parseInt(total, 10) +} + +async function closeAllSequelize (servers: any[]) { + for (let i = 1; i <= servers.length; i++) { + if (sequelizes[ i ]) { + await sequelizes[ i ].close() + delete sequelizes[ i ] + } + } +} + +export { + setVideoField, + setPlaylistField, + setActorField, + countVideoViewsOf, + closeAllSequelize +} diff --git a/shared/extra-utils/miscs/stubs.ts b/shared/extra-utils/miscs/stubs.ts new file mode 100644 index 000000000..d1eb0e3b2 --- /dev/null +++ b/shared/extra-utils/miscs/stubs.ts @@ -0,0 +1,14 @@ +function buildRequestStub (): any { + return { } +} + +function buildResponseStub (): any { + return { + locals: {} + } +} + +export { + buildResponseStub, + buildRequestStub +} diff --git a/shared/extra-utils/overviews/overviews.ts b/shared/extra-utils/overviews/overviews.ts new file mode 100644 index 000000000..23e3ceb1e --- /dev/null +++ b/shared/extra-utils/overviews/overviews.ts @@ -0,0 +1,18 @@ +import { makeGetRequest } from '../requests/requests' + +function getVideosOverview (url: string, useCache = false) { + const path = '/api/v1/overviews/videos' + + const query = { + t: useCache ? undefined : new Date().getTime() + } + + return makeGetRequest({ + url, + path, + query, + statusCodeExpected: 200 + }) +} + +export { getVideosOverview } diff --git a/shared/extra-utils/requests/activitypub.ts b/shared/extra-utils/requests/activitypub.ts new file mode 100644 index 000000000..4762a8665 --- /dev/null +++ b/shared/extra-utils/requests/activitypub.ts @@ -0,0 +1,43 @@ +import { doRequest } from '../../../server/helpers/requests' +import { HTTP_SIGNATURE } from '../../../server/initializers/constants' +import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' +import { activityPubContextify } from '../../../server/helpers/activitypub' + +function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) { + const options = { + method: 'POST', + uri: url, + json: body, + httpSignature, + headers + } + + return doRequest(options) +} + +async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) { + const follow = { + type: 'Follow', + id: by.url + '/toto', + actor: by.url, + object: to.url + } + + const body = activityPubContextify(follow) + + const httpSignature = { + algorithm: HTTP_SIGNATURE.ALGORITHM, + authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, + keyId: by.url, + key: by.privateKey, + headers: HTTP_SIGNATURE.HEADERS_TO_SIGN + } + const headers = buildGlobalHeaders(body) + + return makePOSTAPRequest(to.url, body, httpSignature, headers) +} + +export { + makePOSTAPRequest, + makeFollowRequest +} diff --git a/shared/extra-utils/requests/check-api-params.ts b/shared/extra-utils/requests/check-api-params.ts new file mode 100644 index 000000000..a2a549682 --- /dev/null +++ b/shared/extra-utils/requests/check-api-params.ts @@ -0,0 +1,40 @@ +import { makeGetRequest } from './requests' +import { immutableAssign } from '../miscs/miscs' + +function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { + return makeGetRequest({ + url, + path, + token, + query: immutableAssign(query, { start: 'hello' }), + statusCodeExpected: 400 + }) +} + +function checkBadCountPagination (url: string, path: string, token?: string, query = {}) { + return makeGetRequest({ + url, + path, + token, + query: immutableAssign(query, { count: 'hello' }), + statusCodeExpected: 400 + }) +} + +function checkBadSortPagination (url: string, path: string, token?: string, query = {}) { + return makeGetRequest({ + url, + path, + token, + query: immutableAssign(query, { sort: 'hello' }), + statusCodeExpected: 400 + }) +} + +// --------------------------------------------------------------------------- + +export { + checkBadStartPagination, + checkBadCountPagination, + checkBadSortPagination +} diff --git a/shared/extra-utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts new file mode 100644 index 000000000..3532fb429 --- /dev/null +++ b/shared/extra-utils/requests/requests.ts @@ -0,0 +1,184 @@ +import * as request from 'supertest' +import { buildAbsoluteFixturePath, root } from '../miscs/miscs' +import { isAbsolute, join } from 'path' +import { parse } from 'url' + +function get4KFileUrl () { + return 'https://download.cpy.re/peertube/4k_file.txt' +} + +function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) { + const { host, protocol, pathname } = parse(url) + + return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range }) +} + +function makeGetRequest (options: { + url: string, + path?: string, + query?: any, + token?: string, + statusCodeExpected?: number, + contentType?: string, + range?: string +}) { + if (!options.statusCodeExpected) options.statusCodeExpected = 400 + if (options.contentType === undefined) options.contentType = 'application/json' + + const req = request(options.url).get(options.path) + + if (options.contentType) req.set('Accept', options.contentType) + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + if (options.query) req.query(options.query) + if (options.range) req.set('Range', options.range) + + return req.expect(options.statusCodeExpected) +} + +function makeDeleteRequest (options: { + url: string, + path: string, + token?: string, + statusCodeExpected?: number +}) { + if (!options.statusCodeExpected) options.statusCodeExpected = 400 + + const req = request(options.url) + .delete(options.path) + .set('Accept', 'application/json') + + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + + return req.expect(options.statusCodeExpected) +} + +function makeUploadRequest (options: { + url: string, + method?: 'POST' | 'PUT', + path: string, + token?: string, + fields: { [ fieldName: string ]: any }, + attaches: { [ attachName: string ]: any | any[] }, + statusCodeExpected?: number +}) { + if (!options.statusCodeExpected) options.statusCodeExpected = 400 + + let req: request.Test + if (options.method === 'PUT') { + req = request(options.url).put(options.path) + } else { + req = request(options.url).post(options.path) + } + + req.set('Accept', 'application/json') + + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + + Object.keys(options.fields).forEach(field => { + const value = options.fields[field] + + if (value === undefined) return + + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + req.field(field + '[' + i + ']', value[i]) + } + } else { + req.field(field, value) + } + }) + + Object.keys(options.attaches).forEach(attach => { + const value = options.attaches[attach] + if (Array.isArray(value)) { + req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1]) + } else { + req.attach(attach, buildAbsoluteFixturePath(value)) + } + }) + + return req.expect(options.statusCodeExpected) +} + +function makePostBodyRequest (options: { + url: string, + path: string, + token?: string, + fields?: { [ fieldName: string ]: any }, + statusCodeExpected?: number +}) { + if (!options.fields) options.fields = {} + if (!options.statusCodeExpected) options.statusCodeExpected = 400 + + const req = request(options.url) + .post(options.path) + .set('Accept', 'application/json') + + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + + return req.send(options.fields) + .expect(options.statusCodeExpected) +} + +function makePutBodyRequest (options: { + url: string, + path: string, + token?: string, + fields: { [ fieldName: string ]: any }, + statusCodeExpected?: number +}) { + if (!options.statusCodeExpected) options.statusCodeExpected = 400 + + const req = request(options.url) + .put(options.path) + .set('Accept', 'application/json') + + if (options.token) req.set('Authorization', 'Bearer ' + options.token) + + return req.send(options.fields) + .expect(options.statusCodeExpected) +} + +function makeHTMLRequest (url: string, path: string) { + return request(url) + .get(path) + .set('Accept', 'text/html') + .expect(200) +} + +function updateAvatarRequest (options: { + url: string, + path: string, + accessToken: string, + fixture: string +}) { + let filePath = '' + if (isAbsolute(options.fixture)) { + filePath = options.fixture + } else { + filePath = join(root(), 'server', 'tests', 'fixtures', options.fixture) + } + + return makeUploadRequest({ + url: options.url, + path: options.path, + token: options.accessToken, + fields: {}, + attaches: { avatarfile: filePath }, + statusCodeExpected: 200 + }) +} + +// --------------------------------------------------------------------------- + +export { + get4KFileUrl, + makeHTMLRequest, + makeGetRequest, + makeUploadRequest, + makePostBodyRequest, + makePutBodyRequest, + makeDeleteRequest, + makeRawRequest, + updateAvatarRequest +} diff --git a/shared/extra-utils/search/video-channels.ts b/shared/extra-utils/search/video-channels.ts new file mode 100644 index 000000000..0532134ae --- /dev/null +++ b/shared/extra-utils/search/video-channels.ts @@ -0,0 +1,22 @@ +import { makeGetRequest } from '../requests/requests' + +function searchVideoChannel (url: string, search: string, token?: string, statusCodeExpected = 200) { + const path = '/api/v1/search/video-channels' + + return makeGetRequest({ + url, + path, + query: { + sort: '-createdAt', + search + }, + token, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + searchVideoChannel +} diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts new file mode 100644 index 000000000..ba4627017 --- /dev/null +++ b/shared/extra-utils/search/videos.ts @@ -0,0 +1,77 @@ +/* tslint:disable:no-unused-expression */ + +import * as request from 'supertest' +import { VideosSearchQuery } from '../../models/search' +import { immutableAssign } from '../miscs/miscs' + +function searchVideo (url: string, search: string) { + const path = '/api/v1/search/videos' + const req = request(url) + .get(path) + .query({ sort: '-publishedAt', search }) + .set('Accept', 'application/json') + + return req.expect(200) + .expect('Content-Type', /json/) +} + +function searchVideoWithToken (url: string, search: string, token: string, query: { nsfw?: boolean } = {}) { + const path = '/api/v1/search/videos' + const req = request(url) + .get(path) + .set('Authorization', 'Bearer ' + token) + .query(immutableAssign(query, { sort: '-publishedAt', search })) + .set('Accept', 'application/json') + + return req.expect(200) + .expect('Content-Type', /json/) +} + +function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { + const path = '/api/v1/search/videos' + + const req = request(url) + .get(path) + .query({ start }) + .query({ search }) + .query({ count }) + + if (sort) req.query({ sort }) + + return req.set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function searchVideoWithSort (url: string, search: string, sort: string) { + const path = '/api/v1/search/videos' + + return request(url) + .get(path) + .query({ search }) + .query({ sort }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function advancedVideosSearch (url: string, options: VideosSearchQuery) { + const path = '/api/v1/search/videos' + + return request(url) + .get(path) + .query(options) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +// --------------------------------------------------------------------------- + +export { + searchVideo, + advancedVideosSearch, + searchVideoWithToken, + searchVideoWithPagination, + searchVideoWithSort +} diff --git a/shared/extra-utils/server/activitypub.ts b/shared/extra-utils/server/activitypub.ts new file mode 100644 index 000000000..eccb198ca --- /dev/null +++ b/shared/extra-utils/server/activitypub.ts @@ -0,0 +1,14 @@ +import * as request from 'supertest' + +function makeActivityPubGetRequest (url: string, path: string, expectedStatus = 200) { + return request(url) + .get(path) + .set('Accept', 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8') + .expect(expectedStatus) +} + +// --------------------------------------------------------------------------- + +export { + makeActivityPubGetRequest +} diff --git a/shared/extra-utils/server/clients.ts b/shared/extra-utils/server/clients.ts new file mode 100644 index 000000000..273aac747 --- /dev/null +++ b/shared/extra-utils/server/clients.ts @@ -0,0 +1,19 @@ +import * as request from 'supertest' +import * as urlUtil from 'url' + +function getClient (url: string) { + const path = '/api/v1/oauth-clients/local' + + return request(url) + .get(path) + .set('Host', urlUtil.parse(url).host) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +// --------------------------------------------------------------------------- + +export { + getClient +} diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts new file mode 100644 index 000000000..deb77e9c0 --- /dev/null +++ b/shared/extra-utils/server/config.ts @@ -0,0 +1,156 @@ +import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' +import { CustomConfig } from '../../models/server/custom-config.model' + +function getConfig (url: string) { + const path = '/api/v1/config' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getAbout (url: string) { + const path = '/api/v1/config/about' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getCustomConfig (url: string, token: string, statusCodeExpected = 200) { + const path = '/api/v1/config/custom' + + return makeGetRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = 200) { + const path = '/api/v1/config/custom' + + return makePutBodyRequest({ + url, + token, + path, + fields: newCustomConfig, + statusCodeExpected + }) +} + +function updateCustomSubConfig (url: string, token: string, newConfig: any) { + const updateParams: CustomConfig = { + instance: { + name: 'PeerTube updated', + shortDescription: 'my short description', + description: 'my super description', + terms: 'my super terms', + defaultClientRoute: '/videos/recently-added', + isNSFW: true, + defaultNSFWPolicy: 'blur', + customizations: { + javascript: 'alert("coucou")', + css: 'body { background-color: red; }' + } + }, + services: { + twitter: { + username: '@MySuperUsername', + whitelisted: true + } + }, + cache: { + previews: { + size: 2 + }, + captions: { + size: 3 + } + }, + signup: { + enabled: false, + limit: 5, + requiresEmailVerification: false + }, + admin: { + email: 'superadmin1@example.com' + }, + contactForm: { + enabled: true + }, + user: { + videoQuota: 5242881, + videoQuotaDaily: 318742 + }, + transcoding: { + enabled: true, + allowAdditionalExtensions: true, + threads: 1, + resolutions: { + '240p': false, + '360p': true, + '480p': true, + '720p': false, + '1080p': false + }, + hls: { + enabled: false + } + }, + import: { + videos: { + http: { + enabled: false + }, + torrent: { + enabled: false + } + } + }, + autoBlacklist: { + videos: { + ofUsers: { + enabled: false + } + } + }, + followers: { + instance: { + enabled: true, + manualApproval: false + } + } + } + + Object.assign(updateParams, newConfig) + + return updateCustomConfig(url, token, updateParams) +} + +function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) { + const path = '/api/v1/config/custom' + + return makeDeleteRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + getConfig, + getCustomConfig, + updateCustomConfig, + getAbout, + deleteCustomConfig, + updateCustomSubConfig +} diff --git a/shared/extra-utils/server/contact-form.ts b/shared/extra-utils/server/contact-form.ts new file mode 100644 index 000000000..80394cf99 --- /dev/null +++ b/shared/extra-utils/server/contact-form.ts @@ -0,0 +1,28 @@ +import * as request from 'supertest' +import { ContactForm } from '../../models/server' + +function sendContactForm (options: { + url: string, + fromEmail: string, + fromName: string, + body: string, + expectedStatus?: number +}) { + const path = '/api/v1/server/contact' + + const body: ContactForm = { + fromEmail: options.fromEmail, + fromName: options.fromName, + body: options.body + } + return request(options.url) + .post(path) + .send(body) + .expect(options.expectedStatus || 204) +} + +// --------------------------------------------------------------------------- + +export { + sendContactForm +} diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts new file mode 100644 index 000000000..1505804de --- /dev/null +++ b/shared/extra-utils/server/follows.ts @@ -0,0 +1,111 @@ +import * as request from 'supertest' +import { ServerInfo } from './servers' +import { waitJobs } from './jobs' +import { makeGetRequest, makePostBodyRequest } from '..' + +function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { + const path = '/api/v1/server/followers' + + return request(url) + .get(path) + .query({ start }) + .query({ count }) + .query({ sort }) + .query({ search }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function acceptFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { + const path = '/api/v1/server/followers/' + follower + '/accept' + + return makePostBodyRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +function rejectFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { + const path = '/api/v1/server/followers/' + follower + '/reject' + + return makePostBodyRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { + const path = '/api/v1/server/following' + + return request(url) + .get(path) + .query({ start }) + .query({ count }) + .query({ sort }) + .query({ search }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { + const path = '/api/v1/server/following' + + const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) + return request(follower) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ 'hosts': followingHosts }) + .expect(expectedStatus) +} + +async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { + const path = '/api/v1/server/following/' + target.host + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) +} + +function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = 204) { + const path = '/api/v1/server/followers/peertube@' + follower.host + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) +} + +async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { + await Promise.all([ + follow(server1.url, [ server2.url ], server1.accessToken), + follow(server2.url, [ server1.url ], server2.accessToken) + ]) + + // Wait request propagation + await waitJobs([ server1, server2 ]) + + return true +} + +// --------------------------------------------------------------------------- + +export { + getFollowersListPaginationAndSort, + getFollowingListPaginationAndSort, + unfollow, + removeFollower, + follow, + doubleFollow, + acceptFollower, + rejectFollower +} diff --git a/shared/extra-utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts new file mode 100644 index 000000000..692b5e24d --- /dev/null +++ b/shared/extra-utils/server/jobs.ts @@ -0,0 +1,82 @@ +import * as request from 'supertest' +import { Job, JobState } from '../../models' +import { wait } from '../miscs/miscs' +import { ServerInfo } from './servers' + +function getJobsList (url: string, accessToken: string, state: JobState) { + const path = '/api/v1/jobs/' + state + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) { + const path = '/api/v1/jobs/' + state + + return request(url) + .get(path) + .query({ start }) + .query({ count }) + .query({ sort }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { + const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) : 2000 + let servers: ServerInfo[] + + if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ] + else servers = serversArg as ServerInfo[] + + const states: JobState[] = [ 'waiting', 'active', 'delayed' ] + let pendingRequests = false + + function tasksBuilder () { + const tasks: Promise[] = [] + pendingRequests = false + + // Check if each server has pending request + for (const server of servers) { + for (const state of states) { + const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt') + .then(res => res.body.data) + .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views')) + .then(jobs => { + if (jobs.length !== 0) pendingRequests = true + }) + tasks.push(p) + } + } + + return tasks + } + + do { + await Promise.all(tasksBuilder()) + + // Retry, in case of new jobs were created + if (pendingRequests === false) { + await wait(pendingJobWait) + await Promise.all(tasksBuilder()) + } + + if (pendingRequests) { + await wait(1000) + } + } while (pendingRequests) +} + +// --------------------------------------------------------------------------- + +export { + getJobsList, + waitJobs, + getJobsListPaginationAndSort +} diff --git a/shared/extra-utils/server/redundancy.ts b/shared/extra-utils/server/redundancy.ts new file mode 100644 index 000000000..c39ff2c8b --- /dev/null +++ b/shared/extra-utils/server/redundancy.ts @@ -0,0 +1,17 @@ +import { makePutBodyRequest } from '../requests/requests' + +async function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) { + const path = '/api/v1/server/redundancy/' + host + + return makePutBodyRequest({ + url, + path, + token: accessToken, + fields: { redundancyAllowed }, + statusCodeExpected: expectedStatus + }) +} + +export { + updateRedundancy +} diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts new file mode 100644 index 000000000..5288d253a --- /dev/null +++ b/shared/extra-utils/server/servers.ts @@ -0,0 +1,219 @@ +/* tslint:disable:no-unused-expression */ + +import { ChildProcess, exec, fork } from 'child_process' +import { join } from 'path' +import { root, wait } from '../miscs/miscs' +import { readdir, readFile } from 'fs-extra' +import { existsSync } from 'fs' +import { expect } from 'chai' +import { VideoChannel } from '../../models/videos' + +interface ServerInfo { + app: ChildProcess, + url: string + host: string + serverNumber: number + + client: { + id: string, + secret: string + } + + user: { + username: string, + password: string, + email?: string + } + + accessToken?: string + videoChannel?: VideoChannel + + video?: { + id: number + uuid: string + name: string + account: { + name: string + } + } + + remoteVideo?: { + id: number + uuid: string + } + + videos?: { id: number, uuid: string }[] +} + +function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) { + let apps = [] + let i = 0 + + return new Promise(res => { + function anotherServerDone (serverNumber, app) { + apps[serverNumber - 1] = app + i++ + if (i === totalServers) { + return res(apps) + } + } + + flushTests() + .then(() => { + for (let j = 1; j <= totalServers; j++) { + runServer(j, configOverride).then(app => anotherServerDone(j, app)) + } + }) + }) +} + +function flushTests () { + return new Promise((res, rej) => { + return exec('npm run clean:server:test', err => { + if (err) return rej(err) + + return res() + }) + }) +} + +function runServer (serverNumber: number, configOverride?: Object, args = []) { + const server: ServerInfo = { + app: null, + serverNumber: serverNumber, + url: `http://localhost:${9000 + serverNumber}`, + host: `localhost:${9000 + serverNumber}`, + client: { + id: null, + secret: null + }, + user: { + username: null, + password: null + } + } + + // These actions are async so we need to be sure that they have both been done + const serverRunString = { + 'Server listening': false + } + const key = 'Database peertube_test' + serverNumber + ' is ready' + serverRunString[key] = false + + const regexps = { + client_id: 'Client id: (.+)', + client_secret: 'Client secret: (.+)', + user_username: 'Username: (.+)', + user_password: 'User password: (.+)' + } + + // Share the environment + const env = Object.create(process.env) + env['NODE_ENV'] = 'test' + env['NODE_APP_INSTANCE'] = serverNumber.toString() + + if (configOverride !== undefined) { + env['NODE_CONFIG'] = JSON.stringify(configOverride) + } + + const options = { + silent: true, + env: env, + detached: true + } + + return new Promise(res => { + server.app = fork(join(root(), 'dist', 'server.js'), args, options) + server.app.stdout.on('data', function onStdout (data) { + let dontContinue = false + + // Capture things if we want to + for (const key of Object.keys(regexps)) { + const regexp = regexps[key] + const matches = data.toString().match(regexp) + if (matches !== null) { + if (key === 'client_id') server.client.id = matches[1] + else if (key === 'client_secret') server.client.secret = matches[1] + else if (key === 'user_username') server.user.username = matches[1] + else if (key === 'user_password') server.user.password = matches[1] + } + } + + // Check if all required sentences are here + for (const key of Object.keys(serverRunString)) { + if (data.toString().indexOf(key) !== -1) serverRunString[key] = true + if (serverRunString[key] === false) dontContinue = true + } + + // If no, there is maybe one thing not already initialized (client/user credentials generation...) + if (dontContinue === true) return + + server.app.stdout.removeListener('data', onStdout) + + process.on('exit', () => { + try { + process.kill(server.app.pid) + } catch { /* empty */ } + }) + + res(server) + }) + + }) +} + +async function reRunServer (server: ServerInfo, configOverride?: any) { + const newServer = await runServer(server.serverNumber, configOverride) + server.app = newServer.app + + return server +} + +async function checkTmpIsEmpty (server: ServerInfo) { + return checkDirectoryIsEmpty(server, 'tmp') +} + +async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) { + const testDirectory = 'test' + server.serverNumber + + const directoryPath = join(root(), testDirectory, directory) + + const directoryExists = existsSync(directoryPath) + expect(directoryExists).to.be.true + + const files = await readdir(directoryPath) + expect(files).to.have.lengthOf(0) +} + +function killallServers (servers: ServerInfo[]) { + for (const server of servers) { + process.kill(-server.app.pid) + } +} + +async function waitUntilLog (server: ServerInfo, str: string, count = 1) { + const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log') + + while (true) { + const buf = await readFile(logfile) + + const matches = buf.toString().match(new RegExp(str, 'g')) + if (matches && matches.length === count) return + + await wait(1000) + } +} + +// --------------------------------------------------------------------------- + +export { + checkDirectoryIsEmpty, + checkTmpIsEmpty, + ServerInfo, + flushAndRunMultipleServers, + flushTests, + runServer, + killallServers, + reRunServer, + waitUntilLog +} diff --git a/shared/extra-utils/server/stats.ts b/shared/extra-utils/server/stats.ts new file mode 100644 index 000000000..6f079ad18 --- /dev/null +++ b/shared/extra-utils/server/stats.ts @@ -0,0 +1,22 @@ +import { makeGetRequest } from '../requests/requests' + +function getStats (url: string, useCache = false) { + const path = '/api/v1/server/stats' + + const query = { + t: useCache ? undefined : new Date().getTime() + } + + return makeGetRequest({ + url, + path, + query, + statusCodeExpected: 200 + }) +} + +// --------------------------------------------------------------------------- + +export { + getStats +} diff --git a/shared/extra-utils/socket/socket-io.ts b/shared/extra-utils/socket/socket-io.ts new file mode 100644 index 000000000..854ab71af --- /dev/null +++ b/shared/extra-utils/socket/socket-io.ts @@ -0,0 +1,13 @@ +import * as io from 'socket.io-client' + +function getUserNotificationSocket (serverUrl: string, accessToken: string) { + return io(serverUrl + '/user-notifications', { + query: { accessToken } + }) +} + +// --------------------------------------------------------------------------- + +export { + getUserNotificationSocket +} diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts new file mode 100644 index 000000000..f64a2dbad --- /dev/null +++ b/shared/extra-utils/users/accounts.ts @@ -0,0 +1,80 @@ +/* tslint:disable:no-unused-expression */ + +import * as request from 'supertest' +import { expect } from 'chai' +import { existsSync, readdir } from 'fs-extra' +import { join } from 'path' +import { Account } from '../../models/actors' +import { root } from '../miscs/miscs' +import { makeGetRequest } from '../requests/requests' +import { VideoRateType } from '../../models/videos' + +function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) { + const path = '/api/v1/accounts' + + return makeGetRequest({ + url, + query: { sort }, + path, + statusCodeExpected + }) +} + +function getAccount (url: string, accountName: string, statusCodeExpected = 200) { + const path = '/api/v1/accounts/' + accountName + + return makeGetRequest({ + url, + path, + statusCodeExpected + }) +} + +async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) { + const res = await getAccountsList(url) + const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain) + + const message = `${nameWithDomain} on ${url}` + expect(account.followersCount).to.equal(followersCount, message) + expect(account.followingCount).to.equal(followingCount, message) +} + +async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: number) { + const testDirectory = 'test' + serverNumber + + for (const directory of [ 'avatars' ]) { + const directoryPath = join(root(), testDirectory, directory) + + const directoryExists = existsSync(directoryPath) + expect(directoryExists).to.be.true + + const files = await readdir(directoryPath) + for (const file of files) { + expect(file).to.not.contain(actorUUID) + } + } +} + +function getAccountRatings (url: string, accountName: string, accessToken: string, rating?: VideoRateType, statusCodeExpected = 200) { + const path = '/api/v1/accounts/' + accountName + '/ratings' + + const query = rating ? { rating } : {} + + return request(url) + .get(path) + .query(query) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(statusCodeExpected) + .expect('Content-Type', /json/) +} + +// --------------------------------------------------------------------------- + +export { + getAccount, + expectAccountFollows, + getAccountsList, + checkActorFilesWereRemoved, + getAccountRatings +} diff --git a/shared/extra-utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts new file mode 100644 index 000000000..5feb84179 --- /dev/null +++ b/shared/extra-utils/users/blocklist.ts @@ -0,0 +1,197 @@ +/* tslint:disable:no-unused-expression */ + +import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests' + +function getAccountBlocklistByAccount ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/users/me/blocklist/accounts' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addAccountToAccountBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/blocklist/accounts' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + accountName: accountToBlock + }, + statusCodeExpected + }) +} + +function removeAccountFromAccountBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function getServerBlocklistByAccount ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/users/me/blocklist/servers' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addServerToAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/blocklist/servers' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + host: serverToBlock + }, + statusCodeExpected + }) +} + +function removeServerFromAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function getAccountBlocklistByServer ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/server/blocklist/accounts' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addAccountToServerBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/accounts' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + accountName: accountToBlock + }, + statusCodeExpected + }) +} + +function removeAccountFromServerBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function getServerBlocklistByServer ( + url: string, + token: string, + start: number, + count: number, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/server/blocklist/servers' + + return makeGetRequest({ + url, + token, + query: { start, count, sort }, + path, + statusCodeExpected + }) +} + +function addServerToServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/servers' + + return makePostBodyRequest({ + url, + path, + token, + fields: { + host: serverToBlock + }, + statusCodeExpected + }) +} + +function removeServerFromServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { + const path = '/api/v1/server/blocklist/servers/' + serverToBlock + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + getAccountBlocklistByAccount, + addAccountToAccountBlocklist, + removeAccountFromAccountBlocklist, + getServerBlocklistByAccount, + addServerToAccountBlocklist, + removeServerFromAccountBlocklist, + + getAccountBlocklistByServer, + addAccountToServerBlocklist, + removeAccountFromServerBlocklist, + getServerBlocklistByServer, + addServerToServerBlocklist, + removeServerFromServerBlocklist +} diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts new file mode 100644 index 000000000..ddeb9df2a --- /dev/null +++ b/shared/extra-utils/users/login.ts @@ -0,0 +1,62 @@ +import * as request from 'supertest' + +import { ServerInfo } from '../server/servers' + +type Client = { id: string, secret: string } +type User = { username: string, password: string } +type Server = { url: string, client: Client, user: User } + +function login (url: string, client: Client, user: User, expectedStatus = 200) { + const path = '/api/v1/users/token' + + const body = { + client_id: client.id, + client_secret: client.secret, + username: user.username, + password: user.password, + response_type: 'code', + grant_type: 'password', + scope: 'upload' + } + + return request(url) + .post(path) + .type('form') + .send(body) + .expect(expectedStatus) +} + +async function serverLogin (server: Server) { + const res = await login(server.url, server.client, server.user, 200) + + return res.body.access_token as string +} + +async function userLogin (server: Server, user: User, expectedStatus = 200) { + const res = await login(server.url, server.client, user, expectedStatus) + + return res.body.access_token as string +} + +function setAccessTokensToServers (servers: ServerInfo[]) { + const tasks: Promise[] = [] + + for (const server of servers) { + const p = serverLogin(server).then(t => server.accessToken = t) + tasks.push(p) + } + + return Promise.all(tasks) +} + +// --------------------------------------------------------------------------- + +export { + login, + serverLogin, + userLogin, + setAccessTokensToServers, + Server, + Client, + User +} diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts new file mode 100644 index 000000000..495ff80d9 --- /dev/null +++ b/shared/extra-utils/users/user-notifications.ts @@ -0,0 +1,496 @@ +/* tslint:disable:no-unused-expression */ + +import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' +import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' +import { ServerInfo } from '..' +import { expect } from 'chai' +import { inspect } from 'util' + +function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) { + const path = '/api/v1/users/me/notification-settings' + + return makePutBodyRequest({ + url, + path, + token, + fields: settings, + statusCodeExpected + }) +} + +async function getUserNotifications ( + url: string, + token: string, + start: number, + count: number, + unread?: boolean, + sort = '-createdAt', + statusCodeExpected = 200 +) { + const path = '/api/v1/users/me/notifications' + + return makeGetRequest({ + url, + path, + token, + query: { + start, + count, + sort, + unread + }, + statusCodeExpected + }) +} + +function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) { + const path = '/api/v1/users/me/notifications/read' + + return makePostBodyRequest({ + url, + path, + token, + fields: { ids }, + statusCodeExpected + }) +} +function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/notifications/read-all' + + return makePostBodyRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +async function getLastNotification (serverUrl: string, accessToken: string) { + const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt') + + if (res.body.total === 0) return undefined + + return res.body.data[0] as UserNotification +} + +type CheckerBaseParams = { + server: ServerInfo + emails: object[] + socketNotifications: UserNotification[] + token: string, + check?: { web: boolean, mail: boolean } +} + +type CheckerType = 'presence' | 'absence' + +async function checkNotification ( + base: CheckerBaseParams, + notificationChecker: (notification: UserNotification, type: CheckerType) => void, + emailNotificationFinder: (email: object) => boolean, + checkType: CheckerType +) { + const check = base.check || { web: true, mail: true } + + if (check.web) { + const notification = await getLastNotification(base.server.url, base.token) + + if (notification || checkType !== 'absence') { + notificationChecker(notification, checkType) + } + + const socketNotification = base.socketNotifications.find(n => { + try { + notificationChecker(n, 'presence') + return true + } catch { + return false + } + }) + + if (checkType === 'presence') { + const obj = inspect(base.socketNotifications, { depth: 5 }) + expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined + } else { + const obj = inspect(socketNotification, { depth: 5 }) + expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined + } + } + + if (check.mail) { + // Last email + const email = base.emails + .slice() + .reverse() + .find(e => emailNotificationFinder(e)) + + if (checkType === 'presence') { + expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined + } else { + expect(email, 'The email is present. ' + inspect(email)).to.be.undefined + } + } +} + +function checkVideo (video: any, videoName?: string, videoUUID?: string) { + expect(video.name).to.be.a('string') + expect(video.name).to.not.be.empty + if (videoName) expect(video.name).to.equal(videoName) + + expect(video.uuid).to.be.a('string') + expect(video.uuid).to.not.be.empty + if (videoUUID) expect(video.uuid).to.equal(videoUUID) + + expect(video.id).to.be.a('number') +} + +function checkActor (actor: any) { + expect(actor.displayName).to.be.a('string') + expect(actor.displayName).to.not.be.empty + expect(actor.host).to.not.be.undefined +} + +function checkComment (comment: any, commentId: number, threadId: number) { + expect(comment.id).to.equal(commentId) + expect(comment.threadId).to.equal(threadId) +} + +async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { + const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkVideo(notification.video, videoName, videoUUID) + checkActor(notification.video.channel) + } else { + expect(notification).to.satisfy((n: UserNotification) => { + return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName + }) + } + } + + function emailFinder (email: object) { + const text = email[ 'text' ] + return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1 + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { + const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkVideo(notification.video, videoName, videoUUID) + checkActor(notification.video.channel) + } else { + expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + return text.includes(videoUUID) && text.includes('Your video') + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkMyVideoImportIsFinished ( + base: CheckerBaseParams, + videoName: string, + videoUUID: string, + url: string, + success: boolean, + type: CheckerType +) { + const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + expect(notification.videoImport.targetUrl).to.equal(url) + + if (success) checkVideo(notification.videoImport.video, videoName, videoUUID) + } else { + expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + const toFind = success ? ' finished' : ' error' + + return text.includes(url) && text.includes(toFind) + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) { + const notificationType = UserNotificationType.NEW_USER_REGISTRATION + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkActor(notification.account) + expect(notification.account.name).to.equal(username) + } else { + expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + + return text.includes(' registered ') && text.includes(username) + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkNewActorFollow ( + base: CheckerBaseParams, + followType: 'channel' | 'account', + followerName: string, + followerDisplayName: string, + followingDisplayName: string, + type: CheckerType +) { + const notificationType = UserNotificationType.NEW_FOLLOW + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkActor(notification.actorFollow.follower) + expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName) + expect(notification.actorFollow.follower.name).to.equal(followerName) + expect(notification.actorFollow.follower.host).to.not.be.undefined + + expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName) + expect(notification.actorFollow.following.type).to.equal(followType) + } else { + expect(notification).to.satisfy(n => { + return n.type !== notificationType || + (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName) + }) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + + return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName) + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) { + const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkActor(notification.actorFollow.follower) + expect(notification.actorFollow.follower.name).to.equal('peertube') + expect(notification.actorFollow.follower.host).to.equal(followerHost) + + expect(notification.actorFollow.following.name).to.equal('peertube') + } else { + expect(notification).to.satisfy(n => { + return n.type !== notificationType || n.actorFollow.follower.host !== followerHost + }) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + + return text.includes('instance has a new follower') && text.includes(followerHost) + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkCommentMention ( + base: CheckerBaseParams, + uuid: string, + commentId: number, + threadId: number, + byAccountDisplayName: string, + type: CheckerType +) { + const notificationType = UserNotificationType.COMMENT_MENTION + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkComment(notification.comment, commentId, threadId) + checkActor(notification.comment.account) + expect(notification.comment.account.displayName).to.equal(byAccountDisplayName) + + checkVideo(notification.comment.video, undefined, uuid) + } else { + expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId) + } + } + + function emailFinder (email: object) { + const text: string = email[ 'text' ] + + return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName) + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +let lastEmailCount = 0 +async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { + const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + checkComment(notification.comment, commentId, threadId) + checkActor(notification.comment.account) + checkVideo(notification.comment.video, undefined, uuid) + } else { + expect(notification).to.satisfy((n: UserNotification) => { + return n === undefined || n.comment === undefined || n.comment.id !== commentId + }) + } + } + + const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}` + function emailFinder (email: object) { + return email[ 'text' ].indexOf(commentUrl) !== -1 + } + + await checkNotification(base, notificationChecker, emailFinder, type) + + if (type === 'presence') { + // We cannot detect email duplicates, so check we received another email + expect(base.emails).to.have.length.above(lastEmailCount) + lastEmailCount = base.emails.length + } +} + +async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { + const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + expect(notification.videoAbuse.id).to.be.a('number') + checkVideo(notification.videoAbuse.video, videoName, videoUUID) + } else { + expect(notification).to.satisfy((n: UserNotification) => { + return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID + }) + } + } + + function emailFinder (email: object) { + const text = email[ 'text' ] + return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { + const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS + + function notificationChecker (notification: UserNotification, type: CheckerType) { + if (type === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + expect(notification.video.id).to.be.a('number') + checkVideo(notification.video, videoName, videoUUID) + } else { + expect(notification).to.satisfy((n: UserNotification) => { + return n === undefined || n.video === undefined || n.video.uuid !== videoUUID + }) + } + } + + function emailFinder (email: object) { + const text = email[ 'text' ] + return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1 + } + + await checkNotification(base, notificationChecker, emailFinder, type) +} + +async function checkNewBlacklistOnMyVideo ( + base: CheckerBaseParams, + videoUUID: string, + videoName: string, + blacklistType: 'blacklist' | 'unblacklist' +) { + const notificationType = blacklistType === 'blacklist' + ? UserNotificationType.BLACKLIST_ON_MY_VIDEO + : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO + + function notificationChecker (notification: UserNotification) { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video + + checkVideo(video, videoName, videoUUID) + } + + function emailFinder (email: object) { + const text = email[ 'text' ] + return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 + } + + await checkNotification(base, notificationChecker, emailFinder, 'presence') +} + +// --------------------------------------------------------------------------- + +export { + CheckerBaseParams, + CheckerType, + checkNotification, + markAsReadAllNotifications, + checkMyVideoImportIsFinished, + checkUserRegistered, + checkVideoIsPublished, + checkNewVideoFromSubscription, + checkNewActorFollow, + checkNewCommentOnMyVideo, + checkNewBlacklistOnMyVideo, + checkCommentMention, + updateMyNotificationSettings, + checkNewVideoAbuseForModerators, + checkVideoAutoBlacklistForModerators, + getUserNotifications, + markAsReadNotifications, + getLastNotification, + checkNewInstanceFollower +} diff --git a/shared/extra-utils/users/user-subscriptions.ts b/shared/extra-utils/users/user-subscriptions.ts new file mode 100644 index 000000000..7148fbfca --- /dev/null +++ b/shared/extra-utils/users/user-subscriptions.ts @@ -0,0 +1,82 @@ +import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../requests/requests' + +function addUserSubscription (url: string, token: string, targetUri: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/subscriptions' + + return makePostBodyRequest({ + url, + path, + token, + statusCodeExpected, + fields: { uri: targetUri } + }) +} + +function listUserSubscriptions (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) { + const path = '/api/v1/users/me/subscriptions' + + return makeGetRequest({ + url, + path, + token, + statusCodeExpected, + query: { sort } + }) +} + +function listUserSubscriptionVideos (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) { + const path = '/api/v1/users/me/subscriptions/videos' + + return makeGetRequest({ + url, + path, + token, + statusCodeExpected, + query: { sort } + }) +} + +function getUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 200) { + const path = '/api/v1/users/me/subscriptions/' + uri + + return makeGetRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function removeUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 204) { + const path = '/api/v1/users/me/subscriptions/' + uri + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function areSubscriptionsExist (url: string, token: string, uris: string[], statusCodeExpected = 200) { + const path = '/api/v1/users/me/subscriptions/exist' + + return makeGetRequest({ + url, + path, + query: { 'uris[]': uris }, + token, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + areSubscriptionsExist, + addUserSubscription, + listUserSubscriptions, + getUserSubscription, + listUserSubscriptionVideos, + removeUserSubscription +} diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts new file mode 100644 index 000000000..2bd37b8be --- /dev/null +++ b/shared/extra-utils/users/users.ts @@ -0,0 +1,330 @@ +import * as request from 'supertest' +import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' + +import { UserRole } from '../../index' +import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' +import { ServerInfo, userLogin } from '..' +import { UserAdminFlag } from '../../models/users/user-flag.model' + +type CreateUserArgs = { url: string, + accessToken: string, + username: string, + password: string, + videoQuota?: number, + videoQuotaDaily?: number, + role?: UserRole, + adminFlags?: UserAdminFlag, + specialStatus?: number +} +function createUser (parameters: CreateUserArgs) { + const { + url, + accessToken, + username, + adminFlags, + password = 'password', + videoQuota = 1000000, + videoQuotaDaily = -1, + role = UserRole.USER, + specialStatus = 200 + } = parameters + + const path = '/api/v1/users' + const body = { + username, + password, + role, + adminFlags, + email: username + '@example.com', + videoQuota, + videoQuotaDaily + } + + return request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send(body) + .expect(specialStatus) +} + +async function generateUserAccessToken (server: ServerInfo, username: string) { + const password = 'my super password' + await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) + + return userLogin(server, { username, password }) +} + +function registerUser (url: string, username: string, password: string, specialStatus = 204) { + const path = '/api/v1/users/register' + const body = { + username, + password, + email: username + '@example.com' + } + + return request(url) + .post(path) + .set('Accept', 'application/json') + .send(body) + .expect(specialStatus) +} + +function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) { + const path = '/api/v1/users/me' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(specialStatus) + .expect('Content-Type', /json/) +} + +function deleteMe (url: string, accessToken: string, specialStatus = 204) { + const path = '/api/v1/users/me' + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(specialStatus) +} + +function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) { + const path = '/api/v1/users/me/video-quota-used' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(specialStatus) + .expect('Content-Type', /json/) +} + +function getUserInformation (url: string, accessToken: string, userId: number) { + const path = '/api/v1/users/' + userId + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function getMyUserVideoRating (url: string, accessToken: string, videoId: number | string, specialStatus = 200) { + const path = '/api/v1/users/me/videos/' + videoId + '/rating' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(specialStatus) + .expect('Content-Type', /json/) +} + +function getUsersList (url: string, accessToken: string) { + const path = '/api/v1/users' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { + const path = '/api/v1/users' + + return request(url) + .get(path) + .query({ start }) + .query({ count }) + .query({ sort }) + .query({ search }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function removeUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) { + const path = '/api/v1/users' + + return request(url) + .delete(path + '/' + userId) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) +} + +function blockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204, reason?: string) { + const path = '/api/v1/users' + let body: any + if (reason) body = { reason } + + return request(url) + .post(path + '/' + userId + '/block') + .send(body) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) +} + +function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) { + const path = '/api/v1/users' + + return request(url) + .post(path + '/' + userId + '/unblock') + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) +} + +function updateMyUser (options: { + url: string + accessToken: string + currentPassword?: string + newPassword?: string + nsfwPolicy?: NSFWPolicyType + email?: string + autoPlayVideo?: boolean + displayName?: string + description?: string + videosHistoryEnabled?: boolean +}) { + const path = '/api/v1/users/me' + + const toSend = {} + if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword + if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword + if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy + if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo + if (options.email !== undefined && options.email !== null) toSend['email'] = options.email + if (options.description !== undefined && options.description !== null) toSend['description'] = options.description + if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName + if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { + toSend['videosHistoryEnabled'] = options.videosHistoryEnabled + } + + return makePutBodyRequest({ + url: options.url, + path, + token: options.accessToken, + fields: toSend, + statusCodeExpected: 204 + }) +} + +function updateMyAvatar (options: { + url: string, + accessToken: string, + fixture: string +}) { + const path = '/api/v1/users/me/avatar/pick' + + return updateAvatarRequest(Object.assign(options, { path })) +} + +function updateUser (options: { + url: string + userId: number, + accessToken: string, + email?: string, + emailVerified?: boolean, + videoQuota?: number, + videoQuotaDaily?: number, + password?: string, + adminFlags?: UserAdminFlag, + role?: UserRole +}) { + const path = '/api/v1/users/' + options.userId + + const toSend = {} + if (options.password !== undefined && options.password !== null) toSend['password'] = options.password + if (options.email !== undefined && options.email !== null) toSend['email'] = options.email + if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified + if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota + if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily + if (options.role !== undefined && options.role !== null) toSend['role'] = options.role + if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags + + return makePutBodyRequest({ + url: options.url, + path, + token: options.accessToken, + fields: toSend, + statusCodeExpected: 204 + }) +} + +function askResetPassword (url: string, email: string) { + const path = '/api/v1/users/ask-reset-password' + + return makePostBodyRequest({ + url, + path, + fields: { email }, + statusCodeExpected: 204 + }) +} + +function resetPassword (url: string, userId: number, verificationString: string, password: string, statusCodeExpected = 204) { + const path = '/api/v1/users/' + userId + '/reset-password' + + return makePostBodyRequest({ + url, + path, + fields: { password, verificationString }, + statusCodeExpected + }) +} + +function askSendVerifyEmail (url: string, email: string) { + const path = '/api/v1/users/ask-send-verify-email' + + return makePostBodyRequest({ + url, + path, + fields: { email }, + statusCodeExpected: 204 + }) +} + +function verifyEmail (url: string, userId: number, verificationString: string, statusCodeExpected = 204) { + const path = '/api/v1/users/' + userId + '/verify-email' + + return makePostBodyRequest({ + url, + path, + fields: { verificationString }, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + createUser, + registerUser, + getMyUserInformation, + getMyUserVideoRating, + deleteMe, + getMyUserVideoQuotaUsed, + getUsersList, + getUsersListPaginationAndSort, + removeUser, + updateUser, + updateMyUser, + getUserInformation, + blockUser, + unblockUser, + askResetPassword, + resetPassword, + updateMyAvatar, + askSendVerifyEmail, + generateUserAccessToken, + verifyEmail +} diff --git a/shared/extra-utils/videos/services.ts b/shared/extra-utils/videos/services.ts new file mode 100644 index 000000000..1a53dd4cf --- /dev/null +++ b/shared/extra-utils/videos/services.ts @@ -0,0 +1,23 @@ +import * as request from 'supertest' + +function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) { + const path = '/services/oembed' + const query = { + url: oembedUrl, + format, + maxheight: maxHeight, + maxwidth: maxWidth + } + + return request(url) + .get(path) + .query(query) + .set('Accept', 'application/json') + .expect(200) +} + +// --------------------------------------------------------------------------- + +export { + getOEmbed +} diff --git a/shared/extra-utils/videos/video-abuses.ts b/shared/extra-utils/videos/video-abuses.ts new file mode 100644 index 000000000..7f011ec0f --- /dev/null +++ b/shared/extra-utils/videos/video-abuses.ts @@ -0,0 +1,65 @@ +import * as request from 'supertest' +import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model' +import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests' + +function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) { + const path = '/api/v1/videos/' + videoId + '/abuse' + + return request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .send({ reason }) + .expect(specialStatus) +} + +function getVideoAbusesList (url: string, token: string) { + const path = '/api/v1/videos/abuse' + + return request(url) + .get(path) + .query({ sort: 'createdAt' }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(200) + .expect('Content-Type', /json/) +} + +function updateVideoAbuse ( + url: string, + token: string, + videoId: string | number, + videoAbuseId: number, + body: VideoAbuseUpdate, + statusCodeExpected = 204 +) { + const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId + + return makePutBodyRequest({ + url, + token, + path, + fields: body, + statusCodeExpected + }) +} + +function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) { + const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId + + return makeDeleteRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + reportVideoAbuse, + getVideoAbusesList, + updateVideoAbuse, + deleteVideoAbuse +} diff --git a/shared/extra-utils/videos/video-blacklist.ts b/shared/extra-utils/videos/video-blacklist.ts new file mode 100644 index 000000000..e25a292fc --- /dev/null +++ b/shared/extra-utils/videos/video-blacklist.ts @@ -0,0 +1,72 @@ +import * as request from 'supertest' +import { VideoBlacklistType } from '../../models/videos' +import { makeGetRequest } from '..' + +function addVideoToBlacklist ( + url: string, + token: string, + videoId: number | string, + reason?: string, + unfederate?: boolean, + specialStatus = 204 +) { + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return request(url) + .post(path) + .send({ reason, unfederate }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(specialStatus) +} + +function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) { + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return request(url) + .put(path) + .send({ reason }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(specialStatus) +} + +function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) { + const path = '/api/v1/videos/' + videoId + '/blacklist' + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(specialStatus) +} + +function getBlacklistedVideosList (parameters: { + url: string, + token: string, + sort?: string, + type?: VideoBlacklistType, + specialStatus?: number +}) { + let { url, token, sort, type, specialStatus = 200 } = parameters + const path = '/api/v1/videos/blacklist/' + + const query = { sort, type } + + return makeGetRequest({ + url, + path, + query, + token, + statusCodeExpected: specialStatus + }) +} + +// --------------------------------------------------------------------------- + +export { + addVideoToBlacklist, + removeVideoFromBlacklist, + getBlacklistedVideosList, + updateVideoBlacklist +} diff --git a/shared/extra-utils/videos/video-captions.ts b/shared/extra-utils/videos/video-captions.ts new file mode 100644 index 000000000..8d67f617b --- /dev/null +++ b/shared/extra-utils/videos/video-captions.ts @@ -0,0 +1,71 @@ +import { makeDeleteRequest, makeGetRequest, makeUploadRequest } from '../requests/requests' +import * as request from 'supertest' +import * as chai from 'chai' +import { buildAbsoluteFixturePath } from '../miscs/miscs' + +const expect = chai.expect + +function createVideoCaption (args: { + url: string, + accessToken: string + videoId: string | number + language: string + fixture: string, + mimeType?: string, + statusCodeExpected?: number +}) { + const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language + + const captionfile = buildAbsoluteFixturePath(args.fixture) + const captionfileAttach = args.mimeType ? [ captionfile, { contentType: args.mimeType } ] : captionfile + + return makeUploadRequest({ + method: 'PUT', + url: args.url, + path, + token: args.accessToken, + fields: {}, + attaches: { + captionfile: captionfileAttach + }, + statusCodeExpected: args.statusCodeExpected || 204 + }) +} + +function listVideoCaptions (url: string, videoId: string | number) { + const path = '/api/v1/videos/' + videoId + '/captions' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function deleteVideoCaption (url: string, token: string, videoId: string | number, language: string) { + const path = '/api/v1/videos/' + videoId + '/captions/' + language + + return makeDeleteRequest({ + url, + token, + path, + statusCodeExpected: 204 + }) +} + +async function testCaptionFile (url: string, captionPath: string, containsString: string) { + const res = await request(url) + .get(captionPath) + .expect(200) + + expect(res.text).to.contain(containsString) +} + +// --------------------------------------------------------------------------- + +export { + createVideoCaption, + listVideoCaptions, + testCaptionFile, + deleteVideoCaption +} diff --git a/shared/extra-utils/videos/video-change-ownership.ts b/shared/extra-utils/videos/video-change-ownership.ts new file mode 100644 index 000000000..371d02000 --- /dev/null +++ b/shared/extra-utils/videos/video-change-ownership.ts @@ -0,0 +1,54 @@ +import * as request from 'supertest' + +function changeVideoOwnership (url: string, token: string, videoId: number | string, username, expectedStatus = 204) { + const path = '/api/v1/videos/' + videoId + '/give-ownership' + + return request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .send({ username }) + .expect(expectedStatus) +} + +function getVideoChangeOwnershipList (url: string, token: string) { + const path = '/api/v1/videos/ownership' + + return request(url) + .get(path) + .query({ sort: '-createdAt' }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(200) + .expect('Content-Type', /json/) +} + +function acceptChangeOwnership (url: string, token: string, ownershipId: string, channelId: number, expectedStatus = 204) { + const path = '/api/v1/videos/ownership/' + ownershipId + '/accept' + + return request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .send({ channelId }) + .expect(expectedStatus) +} + +function refuseChangeOwnership (url: string, token: string, ownershipId: string, expectedStatus = 204) { + const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse' + + return request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +// --------------------------------------------------------------------------- + +export { + changeVideoOwnership, + getVideoChangeOwnershipList, + acceptChangeOwnership, + refuseChangeOwnership +} diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts new file mode 100644 index 000000000..93a257bf9 --- /dev/null +++ b/shared/extra-utils/videos/video-channels.ts @@ -0,0 +1,134 @@ +import * as request from 'supertest' +import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' +import { updateAvatarRequest } from '../requests/requests' +import { getMyUserInformation, ServerInfo } from '..' +import { User } from '../..' + +function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { + const path = '/api/v1/video-channels' + + const req = request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + + if (sort) req.query({ sort }) + + return req.set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) { + const path = '/api/v1/accounts/' + accountName + '/video-channels' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .expect(specialStatus) + .expect('Content-Type', /json/) +} + +function addVideoChannel ( + url: string, + token: string, + videoChannelAttributesArg: VideoChannelCreate, + expectedStatus = 200 +) { + const path = '/api/v1/video-channels/' + + // Default attributes + let attributes = { + displayName: 'my super video channel', + description: 'my super channel description', + support: 'my super channel support' + } + attributes = Object.assign(attributes, videoChannelAttributesArg) + + return request(url) + .post(path) + .send(attributes) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function updateVideoChannel ( + url: string, + token: string, + channelName: string, + attributes: VideoChannelUpdate, + expectedStatus = 204 +) { + const body = {} + const path = '/api/v1/video-channels/' + channelName + + if (attributes.displayName) body['displayName'] = attributes.displayName + if (attributes.description) body['description'] = attributes.description + if (attributes.support) body['support'] = attributes.support + + return request(url) + .put(path) + .send(body) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = 204) { + const path = '/api/v1/video-channels/' + channelName + + return request(url) + .delete(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function getVideoChannel (url: string, channelName: string) { + const path = '/api/v1/video-channels/' + channelName + + return request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function updateVideoChannelAvatar (options: { + url: string, + accessToken: string, + fixture: string, + videoChannelName: string | number +}) { + + const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick' + + return updateAvatarRequest(Object.assign(options, { path })) +} + +function setDefaultVideoChannel (servers: ServerInfo[]) { + const tasks: Promise[] = [] + + for (const server of servers) { + const p = getMyUserInformation(server.url, server.accessToken) + .then(res => server.videoChannel = (res.body as User).videoChannels[0]) + + tasks.push(p) + } + + return Promise.all(tasks) +} + +// --------------------------------------------------------------------------- + +export { + updateVideoChannelAvatar, + getVideoChannelsList, + getAccountVideoChannelsList, + addVideoChannel, + updateVideoChannel, + deleteVideoChannel, + getVideoChannel, + setDefaultVideoChannel +} diff --git a/shared/extra-utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts new file mode 100644 index 000000000..0ebf69ced --- /dev/null +++ b/shared/extra-utils/videos/video-comments.ts @@ -0,0 +1,87 @@ +import * as request from 'supertest' +import { makeDeleteRequest } from '../requests/requests' + +function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) { + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + const req = request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + + if (sort) req.query({ sort }) + if (token) req.set('Authorization', 'Bearer ' + token) + + return req.set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) { + const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId + + const req = request(url) + .get(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + return req.expect(200) + .expect('Content-Type', /json/) +} + +function addVideoCommentThread (url: string, token: string, videoId: number | string, text: string, expectedStatus = 200) { + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + return request(url) + .post(path) + .send({ text }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function addVideoCommentReply ( + url: string, + token: string, + videoId: number | string, + inReplyToCommentId: number, + text: string, + expectedStatus = 200 +) { + const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId + + return request(url) + .post(path) + .send({ text }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function deleteVideoComment ( + url: string, + token: string, + videoId: number | string, + commentId: number, + statusCodeExpected = 204 +) { + const path = '/api/v1/videos/' + videoId + '/comments/' + commentId + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +// --------------------------------------------------------------------------- + +export { + getVideoCommentThreads, + getVideoThreadComments, + addVideoCommentThread, + addVideoCommentReply, + deleteVideoComment +} diff --git a/shared/extra-utils/videos/video-history.ts b/shared/extra-utils/videos/video-history.ts new file mode 100644 index 000000000..dc7095b4d --- /dev/null +++ b/shared/extra-utils/videos/video-history.ts @@ -0,0 +1,39 @@ +import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' + +function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number, statusCodeExpected = 204) { + const path = '/api/v1/videos/' + videoId + '/watching' + const fields = { currentTime } + + return makePutBodyRequest({ url, path, token, fields, statusCodeExpected }) +} + +function listMyVideosHistory (url: string, token: string) { + const path = '/api/v1/users/me/history/videos' + + return makeGetRequest({ + url, + path, + token, + statusCodeExpected: 200 + }) +} + +function removeMyVideosHistory (url: string, token: string, beforeDate?: string) { + const path = '/api/v1/users/me/history/videos/remove' + + return makePostBodyRequest({ + url, + path, + token, + fields: beforeDate ? { beforeDate } : {}, + statusCodeExpected: 204 + }) +} + +// --------------------------------------------------------------------------- + +export { + userWatchVideo, + listMyVideosHistory, + removeMyVideosHistory +} diff --git a/shared/extra-utils/videos/video-imports.ts b/shared/extra-utils/videos/video-imports.ts new file mode 100644 index 000000000..ec77cdcda --- /dev/null +++ b/shared/extra-utils/videos/video-imports.ts @@ -0,0 +1,57 @@ + +import { VideoImportCreate } from '../../models/videos' +import { makeGetRequest, makeUploadRequest } from '../requests/requests' + +function getYoutubeVideoUrl () { + return 'https://youtu.be/msX3jv1XdvM' +} + +function getMagnetURI () { + // tslint:disable:max-line-length + return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' +} + +function getBadVideoUrl () { + return 'https://download.cpy.re/peertube/bad_video.mp4' +} + +function importVideo (url: string, token: string, attributes: VideoImportCreate) { + const path = '/api/v1/videos/imports' + + let attaches: any = {} + if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile } + + return makeUploadRequest({ + url, + path, + token, + attaches, + fields: attributes, + statusCodeExpected: 200 + }) +} + +function getMyVideoImports (url: string, token: string, sort?: string) { + const path = '/api/v1/users/me/videos/imports' + + const query = {} + if (sort) query['sort'] = sort + + return makeGetRequest({ + url, + query, + path, + token, + statusCodeExpected: 200 + }) +} + +// --------------------------------------------------------------------------- + +export { + getBadVideoUrl, + getYoutubeVideoUrl, + importVideo, + getMagnetURI, + getMyVideoImports +} diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts new file mode 100644 index 000000000..4d110a131 --- /dev/null +++ b/shared/extra-utils/videos/video-playlists.ts @@ -0,0 +1,318 @@ +import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' +import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model' +import { omit } from 'lodash' +import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model' +import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model' +import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model' +import { videoUUIDToId } from './videos' +import { join } from 'path' +import { root } from '..' +import { readdir } from 'fs-extra' +import { expect } from 'chai' +import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model' + +function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) { + const path = '/api/v1/video-playlists' + + const query = { + start, + count, + sort + } + + return makeGetRequest({ + url, + path, + query, + statusCodeExpected: 200 + }) +} + +function getVideoChannelPlaylistsList (url: string, videoChannelName: string, start: number, count: number, sort?: string) { + const path = '/api/v1/video-channels/' + videoChannelName + '/video-playlists' + + const query = { + start, + count, + sort + } + + return makeGetRequest({ + url, + path, + query, + statusCodeExpected: 200 + }) +} + +function getAccountPlaylistsList (url: string, accountName: string, start: number, count: number, sort?: string) { + const path = '/api/v1/accounts/' + accountName + '/video-playlists' + + const query = { + start, + count, + sort + } + + return makeGetRequest({ + url, + path, + query, + statusCodeExpected: 200 + }) +} + +function getAccountPlaylistsListWithToken ( + url: string, + token: string, + accountName: string, + start: number, + count: number, + playlistType?: VideoPlaylistType, + sort?: string +) { + const path = '/api/v1/accounts/' + accountName + '/video-playlists' + + const query = { + start, + count, + playlistType, + sort + } + + return makeGetRequest({ + url, + token, + path, + query, + statusCodeExpected: 200 + }) +} + +function getVideoPlaylist (url: string, playlistId: number | string, statusCodeExpected = 200) { + const path = '/api/v1/video-playlists/' + playlistId + + return makeGetRequest({ + url, + path, + statusCodeExpected + }) +} + +function getVideoPlaylistWithToken (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) { + const path = '/api/v1/video-playlists/' + playlistId + + return makeGetRequest({ + url, + token, + path, + statusCodeExpected + }) +} + +function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) { + const path = '/api/v1/video-playlists/' + playlistId + + return makeDeleteRequest({ + url, + path, + token, + statusCodeExpected + }) +} + +function createVideoPlaylist (options: { + url: string, + token: string, + playlistAttrs: VideoPlaylistCreate, + expectedStatus?: number +}) { + const path = '/api/v1/video-playlists' + + const fields = omit(options.playlistAttrs, 'thumbnailfile') + + const attaches = options.playlistAttrs.thumbnailfile + ? { thumbnailfile: options.playlistAttrs.thumbnailfile } + : {} + + return makeUploadRequest({ + method: 'POST', + url: options.url, + path, + token: options.token, + fields, + attaches, + statusCodeExpected: options.expectedStatus || 200 + }) +} + +function updateVideoPlaylist (options: { + url: string, + token: string, + playlistAttrs: VideoPlaylistUpdate, + playlistId: number | string, + expectedStatus?: number +}) { + const path = '/api/v1/video-playlists/' + options.playlistId + + const fields = omit(options.playlistAttrs, 'thumbnailfile') + + const attaches = options.playlistAttrs.thumbnailfile + ? { thumbnailfile: options.playlistAttrs.thumbnailfile } + : {} + + return makeUploadRequest({ + method: 'PUT', + url: options.url, + path, + token: options.token, + fields, + attaches, + statusCodeExpected: options.expectedStatus || 204 + }) +} + +async function addVideoInPlaylist (options: { + url: string, + token: string, + playlistId: number | string, + elementAttrs: VideoPlaylistElementCreate | { videoId: string } + expectedStatus?: number +}) { + options.elementAttrs.videoId = await videoUUIDToId(options.url, options.elementAttrs.videoId) + + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' + + return makePostBodyRequest({ + url: options.url, + path, + token: options.token, + fields: options.elementAttrs, + statusCodeExpected: options.expectedStatus || 200 + }) +} + +function updateVideoPlaylistElement (options: { + url: string, + token: string, + playlistId: number | string, + videoId: number | string, + elementAttrs: VideoPlaylistElementUpdate, + expectedStatus?: number +}) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId + + return makePutBodyRequest({ + url: options.url, + path, + token: options.token, + fields: options.elementAttrs, + statusCodeExpected: options.expectedStatus || 204 + }) +} + +function removeVideoFromPlaylist (options: { + url: string, + token: string, + playlistId: number | string, + videoId: number | string, + expectedStatus?: number +}) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId + + return makeDeleteRequest({ + url: options.url, + path, + token: options.token, + statusCodeExpected: options.expectedStatus || 204 + }) +} + +function reorderVideosPlaylist (options: { + url: string, + token: string, + playlistId: number | string, + elementAttrs: { + startPosition: number, + insertAfterPosition: number, + reorderLength?: number + }, + expectedStatus?: number +}) { + const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' + + return makePostBodyRequest({ + url: options.url, + path, + token: options.token, + fields: options.elementAttrs, + statusCodeExpected: options.expectedStatus || 204 + }) +} + +async function checkPlaylistFilesWereRemoved ( + playlistUUID: string, + serverNumber: number, + directories = [ 'thumbnails' ] +) { + const testDirectory = 'test' + serverNumber + + for (const directory of directories) { + const directoryPath = join(root(), testDirectory, directory) + + const files = await readdir(directoryPath) + for (const file of files) { + expect(file).to.not.contain(playlistUUID) + } + } +} + +function getVideoPlaylistPrivacies (url: string) { + const path = '/api/v1/video-playlists/privacies' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function doVideosExistInMyPlaylist (url: string, token: string, videoIds: number[]) { + const path = '/api/v1/users/me/video-playlists/videos-exist' + + return makeGetRequest({ + url, + token, + path, + query: { videoIds }, + statusCodeExpected: 200 + }) +} + +// --------------------------------------------------------------------------- + +export { + getVideoPlaylistPrivacies, + + getVideoPlaylistsList, + getVideoChannelPlaylistsList, + getAccountPlaylistsList, + getAccountPlaylistsListWithToken, + + getVideoPlaylist, + getVideoPlaylistWithToken, + + createVideoPlaylist, + updateVideoPlaylist, + deleteVideoPlaylist, + + addVideoInPlaylist, + updateVideoPlaylistElement, + removeVideoFromPlaylist, + + reorderVideosPlaylist, + + checkPlaylistFilesWereRemoved, + + doVideosExistInMyPlaylist +} diff --git a/shared/extra-utils/videos/video-streaming-playlists.ts b/shared/extra-utils/videos/video-streaming-playlists.ts new file mode 100644 index 000000000..eb25011cb --- /dev/null +++ b/shared/extra-utils/videos/video-streaming-playlists.ts @@ -0,0 +1,51 @@ +import { makeRawRequest } from '../requests/requests' +import { sha256 } from '../../../server/helpers/core-utils' +import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' +import { expect } from 'chai' + +function getPlaylist (url: string, statusCodeExpected = 200) { + return makeRawRequest(url, statusCodeExpected) +} + +function getSegment (url: string, statusCodeExpected = 200, range?: string) { + return makeRawRequest(url, statusCodeExpected, range) +} + +function getSegmentSha256 (url: string, statusCodeExpected = 200) { + return makeRawRequest(url, statusCodeExpected) +} + +async function checkSegmentHash ( + baseUrlPlaylist: string, + baseUrlSegment: string, + videoUUID: string, + resolution: number, + hlsPlaylist: VideoStreamingPlaylist +) { + const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`) + const playlist = res.text + + const videoName = `${videoUUID}-${resolution}-fragmented.mp4` + + const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) + + const length = parseInt(matches[1], 10) + const offset = parseInt(matches[2], 10) + const range = `${offset}-${offset + length - 1}` + + const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`) + + const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) + + const sha256Server = resSha.body[ videoName ][range] + expect(sha256(res2.body)).to.equal(sha256Server) +} + +// --------------------------------------------------------------------------- + +export { + getPlaylist, + getSegment, + getSegmentSha256, + checkSegmentHash +} diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts new file mode 100644 index 000000000..b5a07b792 --- /dev/null +++ b/shared/extra-utils/videos/videos.ts @@ -0,0 +1,648 @@ +/* tslint:disable:no-unused-expression */ + +import { expect } from 'chai' +import { pathExists, readdir, readFile } from 'fs-extra' +import * as parseTorrent from 'parse-torrent' +import { extname, join } from 'path' +import * as request from 'supertest' +import { + buildAbsoluteFixturePath, + getMyUserInformation, + immutableAssign, + makeGetRequest, + makePutBodyRequest, + makeUploadRequest, + root, + ServerInfo, + testImage +} from '../' +import * as validator from 'validator' +import { VideoDetails, VideoPrivacy } from '../../models/videos' +import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, loadLanguages, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' +import { dateIsValid, webtorrentAdd } from '../miscs/miscs' + +loadLanguages() + +type VideoAttributes = { + name?: string + category?: number + licence?: number + language?: string + nsfw?: boolean + commentsEnabled?: boolean + downloadEnabled?: boolean + waitTranscoding?: boolean + description?: string + originallyPublishedAt?: string + tags?: string[] + channelId?: number + privacy?: VideoPrivacy + fixture?: string + thumbnailfile?: string + previewfile?: string + scheduleUpdate?: { + updateAt: string + privacy?: VideoPrivacy + } +} + +function getVideoCategories (url: string) { + const path = '/api/v1/videos/categories' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getVideoLicences (url: string) { + const path = '/api/v1/videos/licences' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getVideoLanguages (url: string) { + const path = '/api/v1/videos/languages' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getVideoPrivacies (url: string) { + const path = '/api/v1/videos/privacies' + + return makeGetRequest({ + url, + path, + statusCodeExpected: 200 + }) +} + +function getVideo (url: string, id: number | string, expectedStatus = 200) { + const path = '/api/v1/videos/' + id + + return request(url) + .get(path) + .set('Accept', 'application/json') + .expect(expectedStatus) +} + +function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) { + const path = '/api/v1/videos/' + id + '/views' + + const req = request(url) + .post(path) + .set('Accept', 'application/json') + + if (xForwardedFor) { + req.set('X-Forwarded-For', xForwardedFor) + } + + return req.expect(expectedStatus) +} + +function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) { + const path = '/api/v1/videos/' + id + + return request(url) + .get(path) + .set('Authorization', 'Bearer ' + token) + .set('Accept', 'application/json') + .expect(expectedStatus) +} + +function getVideoDescription (url: string, descriptionPath: string) { + return request(url) + .get(descriptionPath) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideosList (url: string) { + const path = '/api/v1/videos' + + return request(url) + .get(path) + .query({ sort: 'name' }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) { + const path = '/api/v1/videos' + + return request(url) + .get(path) + .set('Authorization', 'Bearer ' + token) + .query(immutableAssign(query, { sort: 'name' })) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getLocalVideos (url: string) { + const path = '/api/v1/videos' + + return request(url) + .get(path) + .query({ sort: 'name', filter: 'local' }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) { + const path = '/api/v1/users/me/videos' + + const req = request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + + if (sort) req.query({ sort }) + + return req.set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function getAccountVideos ( + url: string, + accessToken: string, + accountName: string, + start: number, + count: number, + sort?: string, + query: { nsfw?: boolean } = {} +) { + const path = '/api/v1/accounts/' + accountName + '/videos' + + return makeGetRequest({ + url, + path, + query: immutableAssign(query, { + start, + count, + sort + }), + token: accessToken, + statusCodeExpected: 200 + }) +} + +function getVideoChannelVideos ( + url: string, + accessToken: string, + videoChannelName: string, + start: number, + count: number, + sort?: string, + query: { nsfw?: boolean } = {} +) { + const path = '/api/v1/video-channels/' + videoChannelName + '/videos' + + return makeGetRequest({ + url, + path, + query: immutableAssign(query, { + start, + count, + sort + }), + token: accessToken, + statusCodeExpected: 200 + }) +} + +function getPlaylistVideos ( + url: string, + accessToken: string, + playlistId: number | string, + start: number, + count: number, + query: { nsfw?: boolean } = {} +) { + const path = '/api/v1/video-playlists/' + playlistId + '/videos' + + return makeGetRequest({ + url, + path, + query: immutableAssign(query, { + start, + count + }), + token: accessToken, + statusCodeExpected: 200 + }) +} + +function getVideosListPagination (url: string, start: number, count: number, sort?: string) { + const path = '/api/v1/videos' + + const req = request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + + if (sort) req.query({ sort }) + + return req.set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideosListSort (url: string, sort: string) { + const path = '/api/v1/videos' + + return request(url) + .get(path) + .query({ sort: sort }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) { + const path = '/api/v1/videos' + + return request(url) + .get(path) + .query(query) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) { + const path = '/api/v1/videos' + + return request(url) + .delete(path + '/' + id) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +async function checkVideoFilesWereRemoved ( + videoUUID: string, + serverNumber: number, + directories = [ + 'redundancy', + 'videos', + 'thumbnails', + 'torrents', + 'previews', + 'captions', + join('playlists', 'hls'), + join('redundancy', 'hls') + ] +) { + const testDirectory = 'test' + serverNumber + + for (const directory of directories) { + const directoryPath = join(root(), testDirectory, directory) + + const directoryExists = await pathExists(directoryPath) + if (directoryExists === false) continue + + const files = await readdir(directoryPath) + for (const file of files) { + expect(file).to.not.contain(videoUUID) + } + } +} + +async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) { + const path = '/api/v1/videos/upload' + let defaultChannelId = '1' + + try { + const res = await getMyUserInformation(url, accessToken) + defaultChannelId = res.body.videoChannels[0].id + } catch (e) { /* empty */ } + + // Override default attributes + const attributes = Object.assign({ + name: 'my super video', + category: 5, + licence: 4, + language: 'zh', + channelId: defaultChannelId, + nsfw: true, + waitTranscoding: false, + description: 'my super description', + support: 'my super support text', + tags: [ 'tag' ], + privacy: VideoPrivacy.PUBLIC, + commentsEnabled: true, + downloadEnabled: true, + fixture: 'video_short.webm' + }, videoAttributesArg) + + const req = request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .field('name', attributes.name) + .field('nsfw', JSON.stringify(attributes.nsfw)) + .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) + .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled)) + .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding)) + .field('privacy', attributes.privacy.toString()) + .field('channelId', attributes.channelId) + + if (attributes.description !== undefined) { + req.field('description', attributes.description) + } + if (attributes.language !== undefined) { + req.field('language', attributes.language.toString()) + } + if (attributes.category !== undefined) { + req.field('category', attributes.category.toString()) + } + if (attributes.licence !== undefined) { + req.field('licence', attributes.licence.toString()) + } + + for (let i = 0; i < attributes.tags.length; i++) { + req.field('tags[' + i + ']', attributes.tags[i]) + } + + if (attributes.thumbnailfile !== undefined) { + req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile)) + } + if (attributes.previewfile !== undefined) { + req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile)) + } + + if (attributes.scheduleUpdate) { + req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt) + + if (attributes.scheduleUpdate.privacy) { + req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy) + } + } + + if (attributes.originallyPublishedAt !== undefined) { + req.field('originallyPublishedAt', attributes.originallyPublishedAt) + } + + return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) + .expect(specialStatus) +} + +function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) { + const path = '/api/v1/videos/' + id + const body = {} + + if (attributes.name) body['name'] = attributes.name + if (attributes.category) body['category'] = attributes.category + if (attributes.licence) body['licence'] = attributes.licence + if (attributes.language) body['language'] = attributes.language + if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw) + if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled) + if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled) + if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt + if (attributes.description) body['description'] = attributes.description + if (attributes.tags) body['tags'] = attributes.tags + if (attributes.privacy) body['privacy'] = attributes.privacy + if (attributes.channelId) body['channelId'] = attributes.channelId + if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate + + // Upload request + if (attributes.thumbnailfile || attributes.previewfile) { + const attaches: any = {} + if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile + if (attributes.previewfile) attaches.previewfile = attributes.previewfile + + return makeUploadRequest({ + url, + method: 'PUT', + path, + token: accessToken, + fields: body, + attaches, + statusCodeExpected + }) + } + + return makePutBodyRequest({ + url, + path, + fields: body, + token: accessToken, + statusCodeExpected + }) +} + +function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) { + const path = '/api/v1/videos/' + id + '/rate' + + return request(url) + .put(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ rating }) + .expect(specialStatus) +} + +function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) { + return new Promise((res, rej) => { + const torrentName = videoUUID + '-' + resolution + '.torrent' + const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName) + readFile(torrentPath, (err, data) => { + if (err) return rej(err) + + return res(parseTorrent(data)) + }) + }) +} + +async function completeVideoCheck ( + url: string, + video: any, + attributes: { + name: string + category: number + licence: number + language: string + nsfw: boolean + commentsEnabled: boolean + downloadEnabled: boolean + description: string + publishedAt?: string + support: string + originallyPublishedAt?: string, + account: { + name: string + host: string + } + isLocal: boolean + tags: string[] + privacy: number + likes?: number + dislikes?: number + duration: number + channel: { + displayName: string + name: string + description + isLocal: boolean + } + fixture: string + files: { + resolution: number + size: number + }[], + thumbnailfile?: string + previewfile?: string + } +) { + if (!attributes.likes) attributes.likes = 0 + if (!attributes.dislikes) attributes.dislikes = 0 + + expect(video.name).to.equal(attributes.name) + expect(video.category.id).to.equal(attributes.category) + expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc') + expect(video.licence.id).to.equal(attributes.licence) + expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown') + expect(video.language.id).to.equal(attributes.language) + expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown') + expect(video.privacy.id).to.deep.equal(attributes.privacy) + expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy]) + expect(video.nsfw).to.equal(attributes.nsfw) + expect(video.description).to.equal(attributes.description) + expect(video.account.id).to.be.a('number') + expect(video.account.uuid).to.be.a('string') + expect(video.account.host).to.equal(attributes.account.host) + expect(video.account.name).to.equal(attributes.account.name) + expect(video.channel.displayName).to.equal(attributes.channel.displayName) + expect(video.channel.name).to.equal(attributes.channel.name) + expect(video.likes).to.equal(attributes.likes) + expect(video.dislikes).to.equal(attributes.dislikes) + expect(video.isLocal).to.equal(attributes.isLocal) + expect(video.duration).to.equal(attributes.duration) + expect(dateIsValid(video.createdAt)).to.be.true + expect(dateIsValid(video.publishedAt)).to.be.true + expect(dateIsValid(video.updatedAt)).to.be.true + + if (attributes.publishedAt) { + expect(video.publishedAt).to.equal(attributes.publishedAt) + } + + if (attributes.originallyPublishedAt) { + expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt) + } else { + expect(video.originallyPublishedAt).to.be.null + } + + const res = await getVideo(url, video.uuid) + const videoDetails: VideoDetails = res.body + + expect(videoDetails.files).to.have.lengthOf(attributes.files.length) + expect(videoDetails.tags).to.deep.equal(attributes.tags) + expect(videoDetails.account.name).to.equal(attributes.account.name) + expect(videoDetails.account.host).to.equal(attributes.account.host) + expect(video.channel.displayName).to.equal(attributes.channel.displayName) + expect(video.channel.name).to.equal(attributes.channel.name) + expect(videoDetails.channel.host).to.equal(attributes.account.host) + expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal) + expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true + expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true + expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled) + expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled) + + for (const attributeFile of attributes.files) { + const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution) + expect(file).not.to.be.undefined + + let extension = extname(attributes.fixture) + // Transcoding enabled on server 2, extension will always be .mp4 + if (attributes.account.host === 'localhost:9002') extension = '.mp4' + + const magnetUri = file.magnetUri + expect(file.magnetUri).to.have.lengthOf.above(2) + expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) + expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`) + expect(file.resolution.id).to.equal(attributeFile.resolution) + expect(file.resolution.label).to.equal(attributeFile.resolution + 'p') + + const minSize = attributeFile.size - ((10 * attributeFile.size) / 100) + const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) + expect(file.size, + 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')') + .to.be.above(minSize).and.below(maxSize) + + { + await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) + } + + if (attributes.previewfile) { + await testImage(url, attributes.previewfile, videoDetails.previewPath) + } + + const torrent = await webtorrentAdd(magnetUri, true) + expect(torrent.files).to.be.an('array') + expect(torrent.files.length).to.equal(1) + expect(torrent.files[0].path).to.exist.and.to.not.equal('') + } +} + +async function videoUUIDToId (url: string, id: number | string) { + if (validator.isUUID('' + id) === false) return id + + const res = await getVideo(url, id) + return res.body.id +} + +async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) { + const videoAttrs: any = { name: options.videoName } + if (options.nsfw) videoAttrs.nsfw = options.nsfw + + const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) + + return { id: res.body.video.id, uuid: res.body.video.uuid } +} + +// --------------------------------------------------------------------------- + +export { + getVideoDescription, + getVideoCategories, + getVideoLicences, + videoUUIDToId, + getVideoPrivacies, + getVideoLanguages, + getMyVideos, + getAccountVideos, + getVideoChannelVideos, + getVideo, + getVideoWithToken, + getVideosList, + getVideosListPagination, + getVideosListSort, + removeVideo, + getVideosListWithToken, + uploadVideo, + getVideosWithFilters, + updateVideo, + rateVideo, + viewVideo, + parseTorrentVideo, + getLocalVideos, + completeVideoCheck, + checkVideoFilesWereRemoved, + getPlaylistVideos, + uploadVideoAndGetId +} diff --git a/shared/utils/cli/cli.ts b/shared/utils/cli/cli.ts deleted file mode 100644 index 54d05e9c6..000000000 --- a/shared/utils/cli/cli.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { exec } from 'child_process' - -import { ServerInfo } from '../server/servers' - -function getEnvCli (server?: ServerInfo) { - return `NODE_ENV=test NODE_APP_INSTANCE=${server.serverNumber}` -} - -async function execCLI (command: string) { - return new Promise((res, rej) => { - exec(command, (err, stdout, stderr) => { - if (err) return rej(err) - - return res(stdout) - }) - }) -} - -// --------------------------------------------------------------------------- - -export { - execCLI, - getEnvCli -} diff --git a/shared/utils/feeds/feeds.ts b/shared/utils/feeds/feeds.ts deleted file mode 100644 index af6df2b20..000000000 --- a/shared/utils/feeds/feeds.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as request from 'supertest' - -type FeedType = 'videos' | 'video-comments' - -function getXMLfeed (url: string, feed: FeedType, format?: string) { - const path = '/feeds/' + feed + '.xml' - - return request(url) - .get(path) - .query((format) ? { format: format } : {}) - .set('Accept', 'application/xml') - .expect(200) - .expect('Content-Type', /xml/) -} - -function getJSONfeed (url: string, feed: FeedType, query: any = {}) { - const path = '/feeds/' + feed + '.json' - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getXMLfeed, - getJSONfeed -} diff --git a/shared/utils/index.ts b/shared/utils/index.ts deleted file mode 100644 index 9d0bbaa38..000000000 --- a/shared/utils/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export * from './server/activitypub' -export * from './cli/cli' -export * from './server/clients' -export * from './server/config' -export * from './server/jobs' -export * from './users/login' -export * from './miscs/miscs' -export * from './miscs/stubs' -export * from './miscs/sql' -export * from './server/follows' -export * from './requests/requests' -export * from './requests/check-api-params' -export * from './server/servers' -export * from './videos/services' -export * from './videos/video-playlists' -export * from './users/users' -export * from './users/accounts' -export * from './videos/video-abuses' -export * from './videos/video-blacklist' -export * from './videos/video-channels' -export * from './videos/video-comments' -export * from './videos/video-streaming-playlists' -export * from './videos/videos' -export * from './videos/video-change-ownership' -export * from './feeds/feeds' -export * from './search/videos' diff --git a/shared/utils/logs/logs.ts b/shared/utils/logs/logs.ts deleted file mode 100644 index cbb1afb93..000000000 --- a/shared/utils/logs/logs.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { makeGetRequest } from '../requests/requests' -import { LogLevel } from '../../models/server/log-level.type' - -function getLogs (url: string, accessToken: string, startDate: Date, endDate?: Date, level?: LogLevel) { - const path = '/api/v1/server/logs' - - return makeGetRequest({ - url, - path, - token: accessToken, - query: { startDate, endDate, level }, - statusCodeExpected: 200 - }) -} - -export { - getLogs -} diff --git a/shared/utils/miscs/email-child-process.js b/shared/utils/miscs/email-child-process.js deleted file mode 100644 index 40ae37d70..000000000 --- a/shared/utils/miscs/email-child-process.js +++ /dev/null @@ -1,27 +0,0 @@ -const MailDev = require('maildev') - -// must run maildev as forked ChildProcess -// failed instantiation stops main process with exit code 0 -process.on('message', (msg) => { - if (msg.start) { - const maildev = new MailDev({ - ip: '127.0.0.1', - smtp: 1025, - disableWeb: true, - silent: true - }) - - maildev.on('new', email => { - process.send({ email }) - }) - - maildev.listen(err => { - if (err) { - // cannot send as Error object - return process.send({ err: err.message }) - } - - return process.send({ err: null }) - }) - } -}) diff --git a/shared/utils/miscs/email.ts b/shared/utils/miscs/email.ts deleted file mode 100644 index f9f1bd95b..000000000 --- a/shared/utils/miscs/email.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { fork, ChildProcess } from 'child_process' - -class MockSmtpServer { - - private static instance: MockSmtpServer - private started = false - private emailChildProcess: ChildProcess - private emails: object[] - - private constructor () { - this.emailChildProcess = fork(`${__dirname}/email-child-process`, []) - - this.emailChildProcess.on('message', (msg) => { - if (msg.email) { - return this.emails.push(msg.email) - } - }) - - process.on('exit', () => this.kill()) - } - - collectEmails (emailsCollection: object[]) { - return new Promise((res, rej) => { - if (this.started) { - this.emails = emailsCollection - return res() - } - - // ensure maildev isn't started until - // unexpected exit can be reported to test runner - this.emailChildProcess.send({ start: true }) - this.emailChildProcess.on('exit', () => { - return rej(new Error('maildev exited unexpectedly, confirm port not in use')) - }) - this.emailChildProcess.on('message', (msg) => { - if (msg.err) { - return rej(new Error(msg.err)) - } - this.started = true - this.emails = emailsCollection - return res() - }) - }) - } - - kill () { - if (!this.emailChildProcess) return - - process.kill(this.emailChildProcess.pid) - - this.emailChildProcess = null - MockSmtpServer.instance = null - } - - static get Instance () { - return this.instance || (this.instance = new this()) - } -} - -// --------------------------------------------------------------------------- - -export { - MockSmtpServer -} diff --git a/shared/utils/miscs/miscs.ts b/shared/utils/miscs/miscs.ts deleted file mode 100644 index 91a93b631..000000000 --- a/shared/utils/miscs/miscs.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import * as chai from 'chai' -import { isAbsolute, join } from 'path' -import * as request from 'supertest' -import * as WebTorrent from 'webtorrent' -import { pathExists, readFile } from 'fs-extra' -import * as ffmpeg from 'fluent-ffmpeg' - -const expect = chai.expect -let webtorrent = new WebTorrent() - -function immutableAssign (target: T, source: U) { - return Object.assign<{}, T, U>({}, target, source) -} - - // Default interval -> 5 minutes -function dateIsValid (dateString: string, interval = 300000) { - const dateToCheck = new Date(dateString) - const now = new Date() - - return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval -} - -function wait (milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)) -} - -function webtorrentAdd (torrent: string, refreshWebTorrent = false) { - if (refreshWebTorrent === true) webtorrent = new WebTorrent() - - return new Promise(res => webtorrent.add(torrent, res)) -} - -function root () { - // We are in /shared/utils/miscs - return join(__dirname, '..', '..', '..') -} - -async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') { - const res = await request(url) - .get(imagePath) - .expect(200) - - const body = res.body - - const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension)) - const minLength = body.length - ((20 * body.length) / 100) - const maxLength = body.length + ((20 * body.length) / 100) - - expect(data.length).to.be.above(minLength) - expect(data.length).to.be.below(maxLength) -} - -function buildAbsoluteFixturePath (path: string, customTravisPath = false) { - if (isAbsolute(path)) { - return path - } - - if (customTravisPath && process.env.TRAVIS) return join(process.env.HOME, 'fixtures', path) - - return join(root(), 'server', 'tests', 'fixtures', path) -} - -async function generateHighBitrateVideo () { - const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true) - - const exists = await pathExists(tempFixturePath) - if (!exists) { - - // Generate a random, high bitrate video on the fly, so we don't have to include - // a large file in the repo. The video needs to have a certain minimum length so - // that FFmpeg properly applies bitrate limits. - // https://stackoverflow.com/a/15795112 - return new Promise(async (res, rej) => { - ffmpeg() - .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) - .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) - .outputOptions([ '-maxrate 10M', '-bufsize 10M' ]) - .output(tempFixturePath) - .on('error', rej) - .on('end', () => res(tempFixturePath)) - .run() - }) - } - - return tempFixturePath -} - -// --------------------------------------------------------------------------- - -export { - dateIsValid, - wait, - webtorrentAdd, - immutableAssign, - testImage, - buildAbsoluteFixturePath, - root, - generateHighBitrateVideo -} diff --git a/shared/utils/miscs/sql.ts b/shared/utils/miscs/sql.ts deleted file mode 100644 index b281471ce..000000000 --- a/shared/utils/miscs/sql.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as Sequelize from 'sequelize' - -let sequelizes: { [ id: number ]: Sequelize.Sequelize } = {} - -function getSequelize (serverNumber: number) { - if (sequelizes[serverNumber]) return sequelizes[serverNumber] - - const dbname = 'peertube_test' + serverNumber - const username = 'peertube' - const password = 'peertube' - const host = 'localhost' - const port = 5432 - - const seq = new Sequelize(dbname, username, password, { - dialect: 'postgres', - host, - port, - operatorsAliases: false, - logging: false - }) - - sequelizes[serverNumber] = seq - - return seq -} - -function setActorField (serverNumber: number, to: string, field: string, value: string) { - const seq = getSequelize(serverNumber) - - const options = { type: Sequelize.QueryTypes.UPDATE } - - return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) -} - -function setVideoField (serverNumber: number, uuid: string, field: string, value: string) { - const seq = getSequelize(serverNumber) - - const options = { type: Sequelize.QueryTypes.UPDATE } - - return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) -} - -function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) { - const seq = getSequelize(serverNumber) - - const options = { type: Sequelize.QueryTypes.UPDATE } - - return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) -} - -async function countVideoViewsOf (serverNumber: number, uuid: string) { - const seq = getSequelize(serverNumber) - - // tslint:disable - const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` - - const options = { type: Sequelize.QueryTypes.SELECT } - const [ { total } ] = await seq.query(query, options) - - if (!total) return 0 - - return parseInt(total, 10) -} - -async function closeAllSequelize (servers: any[]) { - for (let i = 1; i <= servers.length; i++) { - if (sequelizes[ i ]) { - await sequelizes[ i ].close() - delete sequelizes[ i ] - } - } -} - -export { - setVideoField, - setPlaylistField, - setActorField, - countVideoViewsOf, - closeAllSequelize -} diff --git a/shared/utils/miscs/stubs.ts b/shared/utils/miscs/stubs.ts deleted file mode 100644 index d1eb0e3b2..000000000 --- a/shared/utils/miscs/stubs.ts +++ /dev/null @@ -1,14 +0,0 @@ -function buildRequestStub (): any { - return { } -} - -function buildResponseStub (): any { - return { - locals: {} - } -} - -export { - buildResponseStub, - buildRequestStub -} diff --git a/shared/utils/overviews/overviews.ts b/shared/utils/overviews/overviews.ts deleted file mode 100644 index 23e3ceb1e..000000000 --- a/shared/utils/overviews/overviews.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { makeGetRequest } from '../requests/requests' - -function getVideosOverview (url: string, useCache = false) { - const path = '/api/v1/overviews/videos' - - const query = { - t: useCache ? undefined : new Date().getTime() - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: 200 - }) -} - -export { getVideosOverview } diff --git a/shared/utils/requests/activitypub.ts b/shared/utils/requests/activitypub.ts deleted file mode 100644 index 4762a8665..000000000 --- a/shared/utils/requests/activitypub.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { doRequest } from '../../../server/helpers/requests' -import { HTTP_SIGNATURE } from '../../../server/initializers/constants' -import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' -import { activityPubContextify } from '../../../server/helpers/activitypub' - -function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) { - const options = { - method: 'POST', - uri: url, - json: body, - httpSignature, - headers - } - - return doRequest(options) -} - -async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) { - const follow = { - type: 'Follow', - id: by.url + '/toto', - actor: by.url, - object: to.url - } - - const body = activityPubContextify(follow) - - const httpSignature = { - algorithm: HTTP_SIGNATURE.ALGORITHM, - authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, - keyId: by.url, - key: by.privateKey, - headers: HTTP_SIGNATURE.HEADERS_TO_SIGN - } - const headers = buildGlobalHeaders(body) - - return makePOSTAPRequest(to.url, body, httpSignature, headers) -} - -export { - makePOSTAPRequest, - makeFollowRequest -} diff --git a/shared/utils/requests/check-api-params.ts b/shared/utils/requests/check-api-params.ts deleted file mode 100644 index a2a549682..000000000 --- a/shared/utils/requests/check-api-params.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { makeGetRequest } from './requests' -import { immutableAssign } from '../miscs/miscs' - -function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { - return makeGetRequest({ - url, - path, - token, - query: immutableAssign(query, { start: 'hello' }), - statusCodeExpected: 400 - }) -} - -function checkBadCountPagination (url: string, path: string, token?: string, query = {}) { - return makeGetRequest({ - url, - path, - token, - query: immutableAssign(query, { count: 'hello' }), - statusCodeExpected: 400 - }) -} - -function checkBadSortPagination (url: string, path: string, token?: string, query = {}) { - return makeGetRequest({ - url, - path, - token, - query: immutableAssign(query, { sort: 'hello' }), - statusCodeExpected: 400 - }) -} - -// --------------------------------------------------------------------------- - -export { - checkBadStartPagination, - checkBadCountPagination, - checkBadSortPagination -} diff --git a/shared/utils/requests/requests.ts b/shared/utils/requests/requests.ts deleted file mode 100644 index 3532fb429..000000000 --- a/shared/utils/requests/requests.ts +++ /dev/null @@ -1,184 +0,0 @@ -import * as request from 'supertest' -import { buildAbsoluteFixturePath, root } from '../miscs/miscs' -import { isAbsolute, join } from 'path' -import { parse } from 'url' - -function get4KFileUrl () { - return 'https://download.cpy.re/peertube/4k_file.txt' -} - -function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) { - const { host, protocol, pathname } = parse(url) - - return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range }) -} - -function makeGetRequest (options: { - url: string, - path?: string, - query?: any, - token?: string, - statusCodeExpected?: number, - contentType?: string, - range?: string -}) { - if (!options.statusCodeExpected) options.statusCodeExpected = 400 - if (options.contentType === undefined) options.contentType = 'application/json' - - const req = request(options.url).get(options.path) - - if (options.contentType) req.set('Accept', options.contentType) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - if (options.query) req.query(options.query) - if (options.range) req.set('Range', options.range) - - return req.expect(options.statusCodeExpected) -} - -function makeDeleteRequest (options: { - url: string, - path: string, - token?: string, - statusCodeExpected?: number -}) { - if (!options.statusCodeExpected) options.statusCodeExpected = 400 - - const req = request(options.url) - .delete(options.path) - .set('Accept', 'application/json') - - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - return req.expect(options.statusCodeExpected) -} - -function makeUploadRequest (options: { - url: string, - method?: 'POST' | 'PUT', - path: string, - token?: string, - fields: { [ fieldName: string ]: any }, - attaches: { [ attachName: string ]: any | any[] }, - statusCodeExpected?: number -}) { - if (!options.statusCodeExpected) options.statusCodeExpected = 400 - - let req: request.Test - if (options.method === 'PUT') { - req = request(options.url).put(options.path) - } else { - req = request(options.url).post(options.path) - } - - req.set('Accept', 'application/json') - - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - Object.keys(options.fields).forEach(field => { - const value = options.fields[field] - - if (value === undefined) return - - if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - req.field(field + '[' + i + ']', value[i]) - } - } else { - req.field(field, value) - } - }) - - Object.keys(options.attaches).forEach(attach => { - const value = options.attaches[attach] - if (Array.isArray(value)) { - req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1]) - } else { - req.attach(attach, buildAbsoluteFixturePath(value)) - } - }) - - return req.expect(options.statusCodeExpected) -} - -function makePostBodyRequest (options: { - url: string, - path: string, - token?: string, - fields?: { [ fieldName: string ]: any }, - statusCodeExpected?: number -}) { - if (!options.fields) options.fields = {} - if (!options.statusCodeExpected) options.statusCodeExpected = 400 - - const req = request(options.url) - .post(options.path) - .set('Accept', 'application/json') - - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - return req.send(options.fields) - .expect(options.statusCodeExpected) -} - -function makePutBodyRequest (options: { - url: string, - path: string, - token?: string, - fields: { [ fieldName: string ]: any }, - statusCodeExpected?: number -}) { - if (!options.statusCodeExpected) options.statusCodeExpected = 400 - - const req = request(options.url) - .put(options.path) - .set('Accept', 'application/json') - - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - - return req.send(options.fields) - .expect(options.statusCodeExpected) -} - -function makeHTMLRequest (url: string, path: string) { - return request(url) - .get(path) - .set('Accept', 'text/html') - .expect(200) -} - -function updateAvatarRequest (options: { - url: string, - path: string, - accessToken: string, - fixture: string -}) { - let filePath = '' - if (isAbsolute(options.fixture)) { - filePath = options.fixture - } else { - filePath = join(root(), 'server', 'tests', 'fixtures', options.fixture) - } - - return makeUploadRequest({ - url: options.url, - path: options.path, - token: options.accessToken, - fields: {}, - attaches: { avatarfile: filePath }, - statusCodeExpected: 200 - }) -} - -// --------------------------------------------------------------------------- - -export { - get4KFileUrl, - makeHTMLRequest, - makeGetRequest, - makeUploadRequest, - makePostBodyRequest, - makePutBodyRequest, - makeDeleteRequest, - makeRawRequest, - updateAvatarRequest -} diff --git a/shared/utils/search/video-channels.ts b/shared/utils/search/video-channels.ts deleted file mode 100644 index 0532134ae..000000000 --- a/shared/utils/search/video-channels.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { makeGetRequest } from '../requests/requests' - -function searchVideoChannel (url: string, search: string, token?: string, statusCodeExpected = 200) { - const path = '/api/v1/search/video-channels' - - return makeGetRequest({ - url, - path, - query: { - sort: '-createdAt', - search - }, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - searchVideoChannel -} diff --git a/shared/utils/search/videos.ts b/shared/utils/search/videos.ts deleted file mode 100644 index ba4627017..000000000 --- a/shared/utils/search/videos.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import * as request from 'supertest' -import { VideosSearchQuery } from '../../models/search' -import { immutableAssign } from '../miscs/miscs' - -function searchVideo (url: string, search: string) { - const path = '/api/v1/search/videos' - const req = request(url) - .get(path) - .query({ sort: '-publishedAt', search }) - .set('Accept', 'application/json') - - return req.expect(200) - .expect('Content-Type', /json/) -} - -function searchVideoWithToken (url: string, search: string, token: string, query: { nsfw?: boolean } = {}) { - const path = '/api/v1/search/videos' - const req = request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .query(immutableAssign(query, { sort: '-publishedAt', search })) - .set('Accept', 'application/json') - - return req.expect(200) - .expect('Content-Type', /json/) -} - -function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { - const path = '/api/v1/search/videos' - - const req = request(url) - .get(path) - .query({ start }) - .query({ search }) - .query({ count }) - - if (sort) req.query({ sort }) - - return req.set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function searchVideoWithSort (url: string, search: string, sort: string) { - const path = '/api/v1/search/videos' - - return request(url) - .get(path) - .query({ search }) - .query({ sort }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function advancedVideosSearch (url: string, options: VideosSearchQuery) { - const path = '/api/v1/search/videos' - - return request(url) - .get(path) - .query(options) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - searchVideo, - advancedVideosSearch, - searchVideoWithToken, - searchVideoWithPagination, - searchVideoWithSort -} diff --git a/shared/utils/server/activitypub.ts b/shared/utils/server/activitypub.ts deleted file mode 100644 index eccb198ca..000000000 --- a/shared/utils/server/activitypub.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as request from 'supertest' - -function makeActivityPubGetRequest (url: string, path: string, expectedStatus = 200) { - return request(url) - .get(path) - .set('Accept', 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8') - .expect(expectedStatus) -} - -// --------------------------------------------------------------------------- - -export { - makeActivityPubGetRequest -} diff --git a/shared/utils/server/clients.ts b/shared/utils/server/clients.ts deleted file mode 100644 index 273aac747..000000000 --- a/shared/utils/server/clients.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as request from 'supertest' -import * as urlUtil from 'url' - -function getClient (url: string) { - const path = '/api/v1/oauth-clients/local' - - return request(url) - .get(path) - .set('Host', urlUtil.parse(url).host) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getClient -} diff --git a/shared/utils/server/config.ts b/shared/utils/server/config.ts deleted file mode 100644 index deb77e9c0..000000000 --- a/shared/utils/server/config.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' -import { CustomConfig } from '../../models/server/custom-config.model' - -function getConfig (url: string) { - const path = '/api/v1/config' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getAbout (url: string) { - const path = '/api/v1/config/about' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getCustomConfig (url: string, token: string, statusCodeExpected = 200) { - const path = '/api/v1/config/custom' - - return makeGetRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = 200) { - const path = '/api/v1/config/custom' - - return makePutBodyRequest({ - url, - token, - path, - fields: newCustomConfig, - statusCodeExpected - }) -} - -function updateCustomSubConfig (url: string, token: string, newConfig: any) { - const updateParams: CustomConfig = { - instance: { - name: 'PeerTube updated', - shortDescription: 'my short description', - description: 'my super description', - terms: 'my super terms', - defaultClientRoute: '/videos/recently-added', - isNSFW: true, - defaultNSFWPolicy: 'blur', - customizations: { - javascript: 'alert("coucou")', - css: 'body { background-color: red; }' - } - }, - services: { - twitter: { - username: '@MySuperUsername', - whitelisted: true - } - }, - cache: { - previews: { - size: 2 - }, - captions: { - size: 3 - } - }, - signup: { - enabled: false, - limit: 5, - requiresEmailVerification: false - }, - admin: { - email: 'superadmin1@example.com' - }, - contactForm: { - enabled: true - }, - user: { - videoQuota: 5242881, - videoQuotaDaily: 318742 - }, - transcoding: { - enabled: true, - allowAdditionalExtensions: true, - threads: 1, - resolutions: { - '240p': false, - '360p': true, - '480p': true, - '720p': false, - '1080p': false - }, - hls: { - enabled: false - } - }, - import: { - videos: { - http: { - enabled: false - }, - torrent: { - enabled: false - } - } - }, - autoBlacklist: { - videos: { - ofUsers: { - enabled: false - } - } - }, - followers: { - instance: { - enabled: true, - manualApproval: false - } - } - } - - Object.assign(updateParams, newConfig) - - return updateCustomConfig(url, token, updateParams) -} - -function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) { - const path = '/api/v1/config/custom' - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getConfig, - getCustomConfig, - updateCustomConfig, - getAbout, - deleteCustomConfig, - updateCustomSubConfig -} diff --git a/shared/utils/server/contact-form.ts b/shared/utils/server/contact-form.ts deleted file mode 100644 index 80394cf99..000000000 --- a/shared/utils/server/contact-form.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as request from 'supertest' -import { ContactForm } from '../../models/server' - -function sendContactForm (options: { - url: string, - fromEmail: string, - fromName: string, - body: string, - expectedStatus?: number -}) { - const path = '/api/v1/server/contact' - - const body: ContactForm = { - fromEmail: options.fromEmail, - fromName: options.fromName, - body: options.body - } - return request(options.url) - .post(path) - .send(body) - .expect(options.expectedStatus || 204) -} - -// --------------------------------------------------------------------------- - -export { - sendContactForm -} diff --git a/shared/utils/server/follows.ts b/shared/utils/server/follows.ts deleted file mode 100644 index 1505804de..000000000 --- a/shared/utils/server/follows.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as request from 'supertest' -import { ServerInfo } from './servers' -import { waitJobs } from './jobs' -import { makeGetRequest, makePostBodyRequest } from '..' - -function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { - const path = '/api/v1/server/followers' - - return request(url) - .get(path) - .query({ start }) - .query({ count }) - .query({ sort }) - .query({ search }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function acceptFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { - const path = '/api/v1/server/followers/' + follower + '/accept' - - return makePostBodyRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function rejectFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { - const path = '/api/v1/server/followers/' + follower + '/reject' - - return makePostBodyRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { - const path = '/api/v1/server/following' - - return request(url) - .get(path) - .query({ start }) - .query({ count }) - .query({ sort }) - .query({ search }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { - const path = '/api/v1/server/following' - - const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) - return request(follower) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ 'hosts': followingHosts }) - .expect(expectedStatus) -} - -async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { - const path = '/api/v1/server/following/' + target.host - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = 204) { - const path = '/api/v1/server/followers/peertube@' + follower.host - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { - await Promise.all([ - follow(server1.url, [ server2.url ], server1.accessToken), - follow(server2.url, [ server1.url ], server2.accessToken) - ]) - - // Wait request propagation - await waitJobs([ server1, server2 ]) - - return true -} - -// --------------------------------------------------------------------------- - -export { - getFollowersListPaginationAndSort, - getFollowingListPaginationAndSort, - unfollow, - removeFollower, - follow, - doubleFollow, - acceptFollower, - rejectFollower -} diff --git a/shared/utils/server/jobs.ts b/shared/utils/server/jobs.ts deleted file mode 100644 index 692b5e24d..000000000 --- a/shared/utils/server/jobs.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as request from 'supertest' -import { Job, JobState } from '../../models' -import { wait } from '../miscs/miscs' -import { ServerInfo } from './servers' - -function getJobsList (url: string, accessToken: string, state: JobState) { - const path = '/api/v1/jobs/' + state - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) { - const path = '/api/v1/jobs/' + state - - return request(url) - .get(path) - .query({ start }) - .query({ count }) - .query({ sort }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -async function waitJobs (serversArg: ServerInfo[] | ServerInfo) { - const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) : 2000 - let servers: ServerInfo[] - - if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ] - else servers = serversArg as ServerInfo[] - - const states: JobState[] = [ 'waiting', 'active', 'delayed' ] - let pendingRequests = false - - function tasksBuilder () { - const tasks: Promise[] = [] - pendingRequests = false - - // Check if each server has pending request - for (const server of servers) { - for (const state of states) { - const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt') - .then(res => res.body.data) - .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views')) - .then(jobs => { - if (jobs.length !== 0) pendingRequests = true - }) - tasks.push(p) - } - } - - return tasks - } - - do { - await Promise.all(tasksBuilder()) - - // Retry, in case of new jobs were created - if (pendingRequests === false) { - await wait(pendingJobWait) - await Promise.all(tasksBuilder()) - } - - if (pendingRequests) { - await wait(1000) - } - } while (pendingRequests) -} - -// --------------------------------------------------------------------------- - -export { - getJobsList, - waitJobs, - getJobsListPaginationAndSort -} diff --git a/shared/utils/server/redundancy.ts b/shared/utils/server/redundancy.ts deleted file mode 100644 index c39ff2c8b..000000000 --- a/shared/utils/server/redundancy.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { makePutBodyRequest } from '../requests/requests' - -async function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) { - const path = '/api/v1/server/redundancy/' + host - - return makePutBodyRequest({ - url, - path, - token: accessToken, - fields: { redundancyAllowed }, - statusCodeExpected: expectedStatus - }) -} - -export { - updateRedundancy -} diff --git a/shared/utils/server/servers.ts b/shared/utils/server/servers.ts deleted file mode 100644 index 5288d253a..000000000 --- a/shared/utils/server/servers.ts +++ /dev/null @@ -1,219 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import { ChildProcess, exec, fork } from 'child_process' -import { join } from 'path' -import { root, wait } from '../miscs/miscs' -import { readdir, readFile } from 'fs-extra' -import { existsSync } from 'fs' -import { expect } from 'chai' -import { VideoChannel } from '../../models/videos' - -interface ServerInfo { - app: ChildProcess, - url: string - host: string - serverNumber: number - - client: { - id: string, - secret: string - } - - user: { - username: string, - password: string, - email?: string - } - - accessToken?: string - videoChannel?: VideoChannel - - video?: { - id: number - uuid: string - name: string - account: { - name: string - } - } - - remoteVideo?: { - id: number - uuid: string - } - - videos?: { id: number, uuid: string }[] -} - -function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) { - let apps = [] - let i = 0 - - return new Promise(res => { - function anotherServerDone (serverNumber, app) { - apps[serverNumber - 1] = app - i++ - if (i === totalServers) { - return res(apps) - } - } - - flushTests() - .then(() => { - for (let j = 1; j <= totalServers; j++) { - runServer(j, configOverride).then(app => anotherServerDone(j, app)) - } - }) - }) -} - -function flushTests () { - return new Promise((res, rej) => { - return exec('npm run clean:server:test', err => { - if (err) return rej(err) - - return res() - }) - }) -} - -function runServer (serverNumber: number, configOverride?: Object, args = []) { - const server: ServerInfo = { - app: null, - serverNumber: serverNumber, - url: `http://localhost:${9000 + serverNumber}`, - host: `localhost:${9000 + serverNumber}`, - client: { - id: null, - secret: null - }, - user: { - username: null, - password: null - } - } - - // These actions are async so we need to be sure that they have both been done - const serverRunString = { - 'Server listening': false - } - const key = 'Database peertube_test' + serverNumber + ' is ready' - serverRunString[key] = false - - const regexps = { - client_id: 'Client id: (.+)', - client_secret: 'Client secret: (.+)', - user_username: 'Username: (.+)', - user_password: 'User password: (.+)' - } - - // Share the environment - const env = Object.create(process.env) - env['NODE_ENV'] = 'test' - env['NODE_APP_INSTANCE'] = serverNumber.toString() - - if (configOverride !== undefined) { - env['NODE_CONFIG'] = JSON.stringify(configOverride) - } - - const options = { - silent: true, - env: env, - detached: true - } - - return new Promise(res => { - server.app = fork(join(root(), 'dist', 'server.js'), args, options) - server.app.stdout.on('data', function onStdout (data) { - let dontContinue = false - - // Capture things if we want to - for (const key of Object.keys(regexps)) { - const regexp = regexps[key] - const matches = data.toString().match(regexp) - if (matches !== null) { - if (key === 'client_id') server.client.id = matches[1] - else if (key === 'client_secret') server.client.secret = matches[1] - else if (key === 'user_username') server.user.username = matches[1] - else if (key === 'user_password') server.user.password = matches[1] - } - } - - // Check if all required sentences are here - for (const key of Object.keys(serverRunString)) { - if (data.toString().indexOf(key) !== -1) serverRunString[key] = true - if (serverRunString[key] === false) dontContinue = true - } - - // If no, there is maybe one thing not already initialized (client/user credentials generation...) - if (dontContinue === true) return - - server.app.stdout.removeListener('data', onStdout) - - process.on('exit', () => { - try { - process.kill(server.app.pid) - } catch { /* empty */ } - }) - - res(server) - }) - - }) -} - -async function reRunServer (server: ServerInfo, configOverride?: any) { - const newServer = await runServer(server.serverNumber, configOverride) - server.app = newServer.app - - return server -} - -async function checkTmpIsEmpty (server: ServerInfo) { - return checkDirectoryIsEmpty(server, 'tmp') -} - -async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) { - const testDirectory = 'test' + server.serverNumber - - const directoryPath = join(root(), testDirectory, directory) - - const directoryExists = existsSync(directoryPath) - expect(directoryExists).to.be.true - - const files = await readdir(directoryPath) - expect(files).to.have.lengthOf(0) -} - -function killallServers (servers: ServerInfo[]) { - for (const server of servers) { - process.kill(-server.app.pid) - } -} - -async function waitUntilLog (server: ServerInfo, str: string, count = 1) { - const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log') - - while (true) { - const buf = await readFile(logfile) - - const matches = buf.toString().match(new RegExp(str, 'g')) - if (matches && matches.length === count) return - - await wait(1000) - } -} - -// --------------------------------------------------------------------------- - -export { - checkDirectoryIsEmpty, - checkTmpIsEmpty, - ServerInfo, - flushAndRunMultipleServers, - flushTests, - runServer, - killallServers, - reRunServer, - waitUntilLog -} diff --git a/shared/utils/server/stats.ts b/shared/utils/server/stats.ts deleted file mode 100644 index 6f079ad18..000000000 --- a/shared/utils/server/stats.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { makeGetRequest } from '../requests/requests' - -function getStats (url: string, useCache = false) { - const path = '/api/v1/server/stats' - - const query = { - t: useCache ? undefined : new Date().getTime() - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: 200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getStats -} diff --git a/shared/utils/socket/socket-io.ts b/shared/utils/socket/socket-io.ts deleted file mode 100644 index 854ab71af..000000000 --- a/shared/utils/socket/socket-io.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as io from 'socket.io-client' - -function getUserNotificationSocket (serverUrl: string, accessToken: string) { - return io(serverUrl + '/user-notifications', { - query: { accessToken } - }) -} - -// --------------------------------------------------------------------------- - -export { - getUserNotificationSocket -} diff --git a/shared/utils/users/accounts.ts b/shared/utils/users/accounts.ts deleted file mode 100644 index f64a2dbad..000000000 --- a/shared/utils/users/accounts.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import * as request from 'supertest' -import { expect } from 'chai' -import { existsSync, readdir } from 'fs-extra' -import { join } from 'path' -import { Account } from '../../models/actors' -import { root } from '../miscs/miscs' -import { makeGetRequest } from '../requests/requests' -import { VideoRateType } from '../../models/videos' - -function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) { - const path = '/api/v1/accounts' - - return makeGetRequest({ - url, - query: { sort }, - path, - statusCodeExpected - }) -} - -function getAccount (url: string, accountName: string, statusCodeExpected = 200) { - const path = '/api/v1/accounts/' + accountName - - return makeGetRequest({ - url, - path, - statusCodeExpected - }) -} - -async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) { - const res = await getAccountsList(url) - const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain) - - const message = `${nameWithDomain} on ${url}` - expect(account.followersCount).to.equal(followersCount, message) - expect(account.followingCount).to.equal(followingCount, message) -} - -async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: number) { - const testDirectory = 'test' + serverNumber - - for (const directory of [ 'avatars' ]) { - const directoryPath = join(root(), testDirectory, directory) - - const directoryExists = existsSync(directoryPath) - expect(directoryExists).to.be.true - - const files = await readdir(directoryPath) - for (const file of files) { - expect(file).to.not.contain(actorUUID) - } - } -} - -function getAccountRatings (url: string, accountName: string, accessToken: string, rating?: VideoRateType, statusCodeExpected = 200) { - const path = '/api/v1/accounts/' + accountName + '/ratings' - - const query = rating ? { rating } : {} - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(statusCodeExpected) - .expect('Content-Type', /json/) -} - -// --------------------------------------------------------------------------- - -export { - getAccount, - expectAccountFollows, - getAccountsList, - checkActorFilesWereRemoved, - getAccountRatings -} diff --git a/shared/utils/users/blocklist.ts b/shared/utils/users/blocklist.ts deleted file mode 100644 index 5feb84179..000000000 --- a/shared/utils/users/blocklist.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests' - -function getAccountBlocklistByAccount ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = 200 -) { - const path = '/api/v1/users/me/blocklist/accounts' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addAccountToAccountBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/blocklist/accounts' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - accountName: accountToBlock - }, - statusCodeExpected - }) -} - -function removeAccountFromAccountBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getServerBlocklistByAccount ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = 200 -) { - const path = '/api/v1/users/me/blocklist/servers' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addServerToAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/blocklist/servers' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - host: serverToBlock - }, - statusCodeExpected - }) -} - -function removeServerFromAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getAccountBlocklistByServer ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = 200 -) { - const path = '/api/v1/server/blocklist/accounts' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addAccountToServerBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/server/blocklist/accounts' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - accountName: accountToBlock - }, - statusCodeExpected - }) -} - -function removeAccountFromServerBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) { - const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function getServerBlocklistByServer ( - url: string, - token: string, - start: number, - count: number, - sort = '-createdAt', - statusCodeExpected = 200 -) { - const path = '/api/v1/server/blocklist/servers' - - return makeGetRequest({ - url, - token, - query: { start, count, sort }, - path, - statusCodeExpected - }) -} - -function addServerToServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/server/blocklist/servers' - - return makePostBodyRequest({ - url, - path, - token, - fields: { - host: serverToBlock - }, - statusCodeExpected - }) -} - -function removeServerFromServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) { - const path = '/api/v1/server/blocklist/servers/' + serverToBlock - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getAccountBlocklistByAccount, - addAccountToAccountBlocklist, - removeAccountFromAccountBlocklist, - getServerBlocklistByAccount, - addServerToAccountBlocklist, - removeServerFromAccountBlocklist, - - getAccountBlocklistByServer, - addAccountToServerBlocklist, - removeAccountFromServerBlocklist, - getServerBlocklistByServer, - addServerToServerBlocklist, - removeServerFromServerBlocklist -} diff --git a/shared/utils/users/login.ts b/shared/utils/users/login.ts deleted file mode 100644 index ddeb9df2a..000000000 --- a/shared/utils/users/login.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as request from 'supertest' - -import { ServerInfo } from '../server/servers' - -type Client = { id: string, secret: string } -type User = { username: string, password: string } -type Server = { url: string, client: Client, user: User } - -function login (url: string, client: Client, user: User, expectedStatus = 200) { - const path = '/api/v1/users/token' - - const body = { - client_id: client.id, - client_secret: client.secret, - username: user.username, - password: user.password, - response_type: 'code', - grant_type: 'password', - scope: 'upload' - } - - return request(url) - .post(path) - .type('form') - .send(body) - .expect(expectedStatus) -} - -async function serverLogin (server: Server) { - const res = await login(server.url, server.client, server.user, 200) - - return res.body.access_token as string -} - -async function userLogin (server: Server, user: User, expectedStatus = 200) { - const res = await login(server.url, server.client, user, expectedStatus) - - return res.body.access_token as string -} - -function setAccessTokensToServers (servers: ServerInfo[]) { - const tasks: Promise[] = [] - - for (const server of servers) { - const p = serverLogin(server).then(t => server.accessToken = t) - tasks.push(p) - } - - return Promise.all(tasks) -} - -// --------------------------------------------------------------------------- - -export { - login, - serverLogin, - userLogin, - setAccessTokensToServers, - Server, - Client, - User -} diff --git a/shared/utils/users/user-notifications.ts b/shared/utils/users/user-notifications.ts deleted file mode 100644 index 495ff80d9..000000000 --- a/shared/utils/users/user-notifications.ts +++ /dev/null @@ -1,496 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' -import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' -import { ServerInfo } from '..' -import { expect } from 'chai' -import { inspect } from 'util' - -function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) { - const path = '/api/v1/users/me/notification-settings' - - return makePutBodyRequest({ - url, - path, - token, - fields: settings, - statusCodeExpected - }) -} - -async function getUserNotifications ( - url: string, - token: string, - start: number, - count: number, - unread?: boolean, - sort = '-createdAt', - statusCodeExpected = 200 -) { - const path = '/api/v1/users/me/notifications' - - return makeGetRequest({ - url, - path, - token, - query: { - start, - count, - sort, - unread - }, - statusCodeExpected - }) -} - -function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) { - const path = '/api/v1/users/me/notifications/read' - - return makePostBodyRequest({ - url, - path, - token, - fields: { ids }, - statusCodeExpected - }) -} -function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/notifications/read-all' - - return makePostBodyRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -async function getLastNotification (serverUrl: string, accessToken: string) { - const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt') - - if (res.body.total === 0) return undefined - - return res.body.data[0] as UserNotification -} - -type CheckerBaseParams = { - server: ServerInfo - emails: object[] - socketNotifications: UserNotification[] - token: string, - check?: { web: boolean, mail: boolean } -} - -type CheckerType = 'presence' | 'absence' - -async function checkNotification ( - base: CheckerBaseParams, - notificationChecker: (notification: UserNotification, type: CheckerType) => void, - emailNotificationFinder: (email: object) => boolean, - checkType: CheckerType -) { - const check = base.check || { web: true, mail: true } - - if (check.web) { - const notification = await getLastNotification(base.server.url, base.token) - - if (notification || checkType !== 'absence') { - notificationChecker(notification, checkType) - } - - const socketNotification = base.socketNotifications.find(n => { - try { - notificationChecker(n, 'presence') - return true - } catch { - return false - } - }) - - if (checkType === 'presence') { - const obj = inspect(base.socketNotifications, { depth: 5 }) - expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined - } else { - const obj = inspect(socketNotification, { depth: 5 }) - expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined - } - } - - if (check.mail) { - // Last email - const email = base.emails - .slice() - .reverse() - .find(e => emailNotificationFinder(e)) - - if (checkType === 'presence') { - expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined - } else { - expect(email, 'The email is present. ' + inspect(email)).to.be.undefined - } - } -} - -function checkVideo (video: any, videoName?: string, videoUUID?: string) { - expect(video.name).to.be.a('string') - expect(video.name).to.not.be.empty - if (videoName) expect(video.name).to.equal(videoName) - - expect(video.uuid).to.be.a('string') - expect(video.uuid).to.not.be.empty - if (videoUUID) expect(video.uuid).to.equal(videoUUID) - - expect(video.id).to.be.a('number') -} - -function checkActor (actor: any) { - expect(actor.displayName).to.be.a('string') - expect(actor.displayName).to.not.be.empty - expect(actor.host).to.not.be.undefined -} - -function checkComment (comment: any, commentId: number, threadId: number) { - expect(comment.id).to.equal(commentId) - expect(comment.threadId).to.equal(threadId) -} - -async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { - const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkVideo(notification.video, videoName, videoUUID) - checkActor(notification.video.channel) - } else { - expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName - }) - } - } - - function emailFinder (email: object) { - const text = email[ 'text' ] - return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1 - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { - const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkVideo(notification.video, videoName, videoUUID) - checkActor(notification.video.channel) - } else { - expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - return text.includes(videoUUID) && text.includes('Your video') - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkMyVideoImportIsFinished ( - base: CheckerBaseParams, - videoName: string, - videoUUID: string, - url: string, - success: boolean, - type: CheckerType -) { - const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - expect(notification.videoImport.targetUrl).to.equal(url) - - if (success) checkVideo(notification.videoImport.video, videoName, videoUUID) - } else { - expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - const toFind = success ? ' finished' : ' error' - - return text.includes(url) && text.includes(toFind) - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) { - const notificationType = UserNotificationType.NEW_USER_REGISTRATION - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkActor(notification.account) - expect(notification.account.name).to.equal(username) - } else { - expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - - return text.includes(' registered ') && text.includes(username) - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkNewActorFollow ( - base: CheckerBaseParams, - followType: 'channel' | 'account', - followerName: string, - followerDisplayName: string, - followingDisplayName: string, - type: CheckerType -) { - const notificationType = UserNotificationType.NEW_FOLLOW - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkActor(notification.actorFollow.follower) - expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName) - expect(notification.actorFollow.follower.name).to.equal(followerName) - expect(notification.actorFollow.follower.host).to.not.be.undefined - - expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName) - expect(notification.actorFollow.following.type).to.equal(followType) - } else { - expect(notification).to.satisfy(n => { - return n.type !== notificationType || - (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName) - }) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - - return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName) - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) { - const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkActor(notification.actorFollow.follower) - expect(notification.actorFollow.follower.name).to.equal('peertube') - expect(notification.actorFollow.follower.host).to.equal(followerHost) - - expect(notification.actorFollow.following.name).to.equal('peertube') - } else { - expect(notification).to.satisfy(n => { - return n.type !== notificationType || n.actorFollow.follower.host !== followerHost - }) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - - return text.includes('instance has a new follower') && text.includes(followerHost) - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkCommentMention ( - base: CheckerBaseParams, - uuid: string, - commentId: number, - threadId: number, - byAccountDisplayName: string, - type: CheckerType -) { - const notificationType = UserNotificationType.COMMENT_MENTION - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkComment(notification.comment, commentId, threadId) - checkActor(notification.comment.account) - expect(notification.comment.account.displayName).to.equal(byAccountDisplayName) - - checkVideo(notification.comment.video, undefined, uuid) - } else { - expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId) - } - } - - function emailFinder (email: object) { - const text: string = email[ 'text' ] - - return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName) - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -let lastEmailCount = 0 -async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { - const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - checkComment(notification.comment, commentId, threadId) - checkActor(notification.comment.account) - checkVideo(notification.comment.video, undefined, uuid) - } else { - expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.comment === undefined || n.comment.id !== commentId - }) - } - } - - const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}` - function emailFinder (email: object) { - return email[ 'text' ].indexOf(commentUrl) !== -1 - } - - await checkNotification(base, notificationChecker, emailFinder, type) - - if (type === 'presence') { - // We cannot detect email duplicates, so check we received another email - expect(base.emails).to.have.length.above(lastEmailCount) - lastEmailCount = base.emails.length - } -} - -async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { - const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - expect(notification.videoAbuse.id).to.be.a('number') - checkVideo(notification.videoAbuse.video, videoName, videoUUID) - } else { - expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID - }) - } - } - - function emailFinder (email: object) { - const text = email[ 'text' ] - return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { - const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS - - function notificationChecker (notification: UserNotification, type: CheckerType) { - if (type === 'presence') { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - expect(notification.video.id).to.be.a('number') - checkVideo(notification.video, videoName, videoUUID) - } else { - expect(notification).to.satisfy((n: UserNotification) => { - return n === undefined || n.video === undefined || n.video.uuid !== videoUUID - }) - } - } - - function emailFinder (email: object) { - const text = email[ 'text' ] - return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1 - } - - await checkNotification(base, notificationChecker, emailFinder, type) -} - -async function checkNewBlacklistOnMyVideo ( - base: CheckerBaseParams, - videoUUID: string, - videoName: string, - blacklistType: 'blacklist' | 'unblacklist' -) { - const notificationType = blacklistType === 'blacklist' - ? UserNotificationType.BLACKLIST_ON_MY_VIDEO - : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO - - function notificationChecker (notification: UserNotification) { - expect(notification).to.not.be.undefined - expect(notification.type).to.equal(notificationType) - - const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video - - checkVideo(video, videoName, videoUUID) - } - - function emailFinder (email: object) { - const text = email[ 'text' ] - return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 - } - - await checkNotification(base, notificationChecker, emailFinder, 'presence') -} - -// --------------------------------------------------------------------------- - -export { - CheckerBaseParams, - CheckerType, - checkNotification, - markAsReadAllNotifications, - checkMyVideoImportIsFinished, - checkUserRegistered, - checkVideoIsPublished, - checkNewVideoFromSubscription, - checkNewActorFollow, - checkNewCommentOnMyVideo, - checkNewBlacklistOnMyVideo, - checkCommentMention, - updateMyNotificationSettings, - checkNewVideoAbuseForModerators, - checkVideoAutoBlacklistForModerators, - getUserNotifications, - markAsReadNotifications, - getLastNotification, - checkNewInstanceFollower -} diff --git a/shared/utils/users/user-subscriptions.ts b/shared/utils/users/user-subscriptions.ts deleted file mode 100644 index 7148fbfca..000000000 --- a/shared/utils/users/user-subscriptions.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../requests/requests' - -function addUserSubscription (url: string, token: string, targetUri: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/subscriptions' - - return makePostBodyRequest({ - url, - path, - token, - statusCodeExpected, - fields: { uri: targetUri } - }) -} - -function listUserSubscriptions (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) { - const path = '/api/v1/users/me/subscriptions' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected, - query: { sort } - }) -} - -function listUserSubscriptionVideos (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) { - const path = '/api/v1/users/me/subscriptions/videos' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected, - query: { sort } - }) -} - -function getUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 200) { - const path = '/api/v1/users/me/subscriptions/' + uri - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function removeUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 204) { - const path = '/api/v1/users/me/subscriptions/' + uri - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function areSubscriptionsExist (url: string, token: string, uris: string[], statusCodeExpected = 200) { - const path = '/api/v1/users/me/subscriptions/exist' - - return makeGetRequest({ - url, - path, - query: { 'uris[]': uris }, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - areSubscriptionsExist, - addUserSubscription, - listUserSubscriptions, - getUserSubscription, - listUserSubscriptionVideos, - removeUserSubscription -} diff --git a/shared/utils/users/users.ts b/shared/utils/users/users.ts deleted file mode 100644 index 2bd37b8be..000000000 --- a/shared/utils/users/users.ts +++ /dev/null @@ -1,330 +0,0 @@ -import * as request from 'supertest' -import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' - -import { UserRole } from '../../index' -import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' -import { ServerInfo, userLogin } from '..' -import { UserAdminFlag } from '../../models/users/user-flag.model' - -type CreateUserArgs = { url: string, - accessToken: string, - username: string, - password: string, - videoQuota?: number, - videoQuotaDaily?: number, - role?: UserRole, - adminFlags?: UserAdminFlag, - specialStatus?: number -} -function createUser (parameters: CreateUserArgs) { - const { - url, - accessToken, - username, - adminFlags, - password = 'password', - videoQuota = 1000000, - videoQuotaDaily = -1, - role = UserRole.USER, - specialStatus = 200 - } = parameters - - const path = '/api/v1/users' - const body = { - username, - password, - role, - adminFlags, - email: username + '@example.com', - videoQuota, - videoQuotaDaily - } - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send(body) - .expect(specialStatus) -} - -async function generateUserAccessToken (server: ServerInfo, username: string) { - const password = 'my super password' - await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) - - return userLogin(server, { username, password }) -} - -function registerUser (url: string, username: string, password: string, specialStatus = 204) { - const path = '/api/v1/users/register' - const body = { - username, - password, - email: username + '@example.com' - } - - return request(url) - .post(path) - .set('Accept', 'application/json') - .send(body) - .expect(specialStatus) -} - -function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) { - const path = '/api/v1/users/me' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function deleteMe (url: string, accessToken: string, specialStatus = 204) { - const path = '/api/v1/users/me' - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) -} - -function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) { - const path = '/api/v1/users/me/video-quota-used' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function getUserInformation (url: string, accessToken: string, userId: number) { - const path = '/api/v1/users/' + userId - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -function getMyUserVideoRating (url: string, accessToken: string, videoId: number | string, specialStatus = 200) { - const path = '/api/v1/users/me/videos/' + videoId + '/rating' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function getUsersList (url: string, accessToken: string) { - const path = '/api/v1/users' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { - const path = '/api/v1/users' - - return request(url) - .get(path) - .query({ start }) - .query({ count }) - .query({ sort }) - .query({ search }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -function removeUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) { - const path = '/api/v1/users' - - return request(url) - .delete(path + '/' + userId) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function blockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204, reason?: string) { - const path = '/api/v1/users' - let body: any - if (reason) body = { reason } - - return request(url) - .post(path + '/' + userId + '/block') - .send(body) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) { - const path = '/api/v1/users' - - return request(url) - .post(path + '/' + userId + '/unblock') - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) -} - -function updateMyUser (options: { - url: string - accessToken: string - currentPassword?: string - newPassword?: string - nsfwPolicy?: NSFWPolicyType - email?: string - autoPlayVideo?: boolean - displayName?: string - description?: string - videosHistoryEnabled?: boolean -}) { - const path = '/api/v1/users/me' - - const toSend = {} - if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword - if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword - if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy - if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo - if (options.email !== undefined && options.email !== null) toSend['email'] = options.email - if (options.description !== undefined && options.description !== null) toSend['description'] = options.description - if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName - if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { - toSend['videosHistoryEnabled'] = options.videosHistoryEnabled - } - - return makePutBodyRequest({ - url: options.url, - path, - token: options.accessToken, - fields: toSend, - statusCodeExpected: 204 - }) -} - -function updateMyAvatar (options: { - url: string, - accessToken: string, - fixture: string -}) { - const path = '/api/v1/users/me/avatar/pick' - - return updateAvatarRequest(Object.assign(options, { path })) -} - -function updateUser (options: { - url: string - userId: number, - accessToken: string, - email?: string, - emailVerified?: boolean, - videoQuota?: number, - videoQuotaDaily?: number, - password?: string, - adminFlags?: UserAdminFlag, - role?: UserRole -}) { - const path = '/api/v1/users/' + options.userId - - const toSend = {} - if (options.password !== undefined && options.password !== null) toSend['password'] = options.password - if (options.email !== undefined && options.email !== null) toSend['email'] = options.email - if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified - if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota - if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily - if (options.role !== undefined && options.role !== null) toSend['role'] = options.role - if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags - - return makePutBodyRequest({ - url: options.url, - path, - token: options.accessToken, - fields: toSend, - statusCodeExpected: 204 - }) -} - -function askResetPassword (url: string, email: string) { - const path = '/api/v1/users/ask-reset-password' - - return makePostBodyRequest({ - url, - path, - fields: { email }, - statusCodeExpected: 204 - }) -} - -function resetPassword (url: string, userId: number, verificationString: string, password: string, statusCodeExpected = 204) { - const path = '/api/v1/users/' + userId + '/reset-password' - - return makePostBodyRequest({ - url, - path, - fields: { password, verificationString }, - statusCodeExpected - }) -} - -function askSendVerifyEmail (url: string, email: string) { - const path = '/api/v1/users/ask-send-verify-email' - - return makePostBodyRequest({ - url, - path, - fields: { email }, - statusCodeExpected: 204 - }) -} - -function verifyEmail (url: string, userId: number, verificationString: string, statusCodeExpected = 204) { - const path = '/api/v1/users/' + userId + '/verify-email' - - return makePostBodyRequest({ - url, - path, - fields: { verificationString }, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - createUser, - registerUser, - getMyUserInformation, - getMyUserVideoRating, - deleteMe, - getMyUserVideoQuotaUsed, - getUsersList, - getUsersListPaginationAndSort, - removeUser, - updateUser, - updateMyUser, - getUserInformation, - blockUser, - unblockUser, - askResetPassword, - resetPassword, - updateMyAvatar, - askSendVerifyEmail, - generateUserAccessToken, - verifyEmail -} diff --git a/shared/utils/videos/services.ts b/shared/utils/videos/services.ts deleted file mode 100644 index 1a53dd4cf..000000000 --- a/shared/utils/videos/services.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as request from 'supertest' - -function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) { - const path = '/services/oembed' - const query = { - url: oembedUrl, - format, - maxheight: maxHeight, - maxwidth: maxWidth - } - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(200) -} - -// --------------------------------------------------------------------------- - -export { - getOEmbed -} diff --git a/shared/utils/videos/video-abuses.ts b/shared/utils/videos/video-abuses.ts deleted file mode 100644 index 7f011ec0f..000000000 --- a/shared/utils/videos/video-abuses.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as request from 'supertest' -import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model' -import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests' - -function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) { - const path = '/api/v1/videos/' + videoId + '/abuse' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .send({ reason }) - .expect(specialStatus) -} - -function getVideoAbusesList (url: string, token: string) { - const path = '/api/v1/videos/abuse' - - return request(url) - .get(path) - .query({ sort: 'createdAt' }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(200) - .expect('Content-Type', /json/) -} - -function updateVideoAbuse ( - url: string, - token: string, - videoId: string | number, - videoAbuseId: number, - body: VideoAbuseUpdate, - statusCodeExpected = 204 -) { - const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId - - return makePutBodyRequest({ - url, - token, - path, - fields: body, - statusCodeExpected - }) -} - -function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) { - const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - reportVideoAbuse, - getVideoAbusesList, - updateVideoAbuse, - deleteVideoAbuse -} diff --git a/shared/utils/videos/video-blacklist.ts b/shared/utils/videos/video-blacklist.ts deleted file mode 100644 index e25a292fc..000000000 --- a/shared/utils/videos/video-blacklist.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as request from 'supertest' -import { VideoBlacklistType } from '../../models/videos' -import { makeGetRequest } from '..' - -function addVideoToBlacklist ( - url: string, - token: string, - videoId: number | string, - reason?: string, - unfederate?: boolean, - specialStatus = 204 -) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .post(path) - .send({ reason, unfederate }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .put(path) - .send({ reason }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) { - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(specialStatus) -} - -function getBlacklistedVideosList (parameters: { - url: string, - token: string, - sort?: string, - type?: VideoBlacklistType, - specialStatus?: number -}) { - let { url, token, sort, type, specialStatus = 200 } = parameters - const path = '/api/v1/videos/blacklist/' - - const query = { sort, type } - - return makeGetRequest({ - url, - path, - query, - token, - statusCodeExpected: specialStatus - }) -} - -// --------------------------------------------------------------------------- - -export { - addVideoToBlacklist, - removeVideoFromBlacklist, - getBlacklistedVideosList, - updateVideoBlacklist -} diff --git a/shared/utils/videos/video-captions.ts b/shared/utils/videos/video-captions.ts deleted file mode 100644 index 8d67f617b..000000000 --- a/shared/utils/videos/video-captions.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makeUploadRequest } from '../requests/requests' -import * as request from 'supertest' -import * as chai from 'chai' -import { buildAbsoluteFixturePath } from '../miscs/miscs' - -const expect = chai.expect - -function createVideoCaption (args: { - url: string, - accessToken: string - videoId: string | number - language: string - fixture: string, - mimeType?: string, - statusCodeExpected?: number -}) { - const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language - - const captionfile = buildAbsoluteFixturePath(args.fixture) - const captionfileAttach = args.mimeType ? [ captionfile, { contentType: args.mimeType } ] : captionfile - - return makeUploadRequest({ - method: 'PUT', - url: args.url, - path, - token: args.accessToken, - fields: {}, - attaches: { - captionfile: captionfileAttach - }, - statusCodeExpected: args.statusCodeExpected || 204 - }) -} - -function listVideoCaptions (url: string, videoId: string | number) { - const path = '/api/v1/videos/' + videoId + '/captions' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function deleteVideoCaption (url: string, token: string, videoId: string | number, language: string) { - const path = '/api/v1/videos/' + videoId + '/captions/' + language - - return makeDeleteRequest({ - url, - token, - path, - statusCodeExpected: 204 - }) -} - -async function testCaptionFile (url: string, captionPath: string, containsString: string) { - const res = await request(url) - .get(captionPath) - .expect(200) - - expect(res.text).to.contain(containsString) -} - -// --------------------------------------------------------------------------- - -export { - createVideoCaption, - listVideoCaptions, - testCaptionFile, - deleteVideoCaption -} diff --git a/shared/utils/videos/video-change-ownership.ts b/shared/utils/videos/video-change-ownership.ts deleted file mode 100644 index 371d02000..000000000 --- a/shared/utils/videos/video-change-ownership.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as request from 'supertest' - -function changeVideoOwnership (url: string, token: string, videoId: number | string, username, expectedStatus = 204) { - const path = '/api/v1/videos/' + videoId + '/give-ownership' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .send({ username }) - .expect(expectedStatus) -} - -function getVideoChangeOwnershipList (url: string, token: string) { - const path = '/api/v1/videos/ownership' - - return request(url) - .get(path) - .query({ sort: '-createdAt' }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(200) - .expect('Content-Type', /json/) -} - -function acceptChangeOwnership (url: string, token: string, ownershipId: string, channelId: number, expectedStatus = 204) { - const path = '/api/v1/videos/ownership/' + ownershipId + '/accept' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .send({ channelId }) - .expect(expectedStatus) -} - -function refuseChangeOwnership (url: string, token: string, ownershipId: string, expectedStatus = 204) { - const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse' - - return request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -// --------------------------------------------------------------------------- - -export { - changeVideoOwnership, - getVideoChangeOwnershipList, - acceptChangeOwnership, - refuseChangeOwnership -} diff --git a/shared/utils/videos/video-channels.ts b/shared/utils/videos/video-channels.ts deleted file mode 100644 index 93a257bf9..000000000 --- a/shared/utils/videos/video-channels.ts +++ /dev/null @@ -1,134 +0,0 @@ -import * as request from 'supertest' -import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' -import { updateAvatarRequest } from '../requests/requests' -import { getMyUserInformation, ServerInfo } from '..' -import { User } from '../..' - -function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { - const path = '/api/v1/video-channels' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - - return req.set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) { - const path = '/api/v1/accounts/' + accountName + '/video-channels' - - return request(url) - .get(path) - .set('Accept', 'application/json') - .expect(specialStatus) - .expect('Content-Type', /json/) -} - -function addVideoChannel ( - url: string, - token: string, - videoChannelAttributesArg: VideoChannelCreate, - expectedStatus = 200 -) { - const path = '/api/v1/video-channels/' - - // Default attributes - let attributes = { - displayName: 'my super video channel', - description: 'my super channel description', - support: 'my super channel support' - } - attributes = Object.assign(attributes, videoChannelAttributesArg) - - return request(url) - .post(path) - .send(attributes) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function updateVideoChannel ( - url: string, - token: string, - channelName: string, - attributes: VideoChannelUpdate, - expectedStatus = 204 -) { - const body = {} - const path = '/api/v1/video-channels/' + channelName - - if (attributes.displayName) body['displayName'] = attributes.displayName - if (attributes.description) body['description'] = attributes.description - if (attributes.support) body['support'] = attributes.support - - return request(url) - .put(path) - .send(body) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = 204) { - const path = '/api/v1/video-channels/' + channelName - - return request(url) - .delete(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function getVideoChannel (url: string, channelName: string) { - const path = '/api/v1/video-channels/' + channelName - - return request(url) - .get(path) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function updateVideoChannelAvatar (options: { - url: string, - accessToken: string, - fixture: string, - videoChannelName: string | number -}) { - - const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick' - - return updateAvatarRequest(Object.assign(options, { path })) -} - -function setDefaultVideoChannel (servers: ServerInfo[]) { - const tasks: Promise[] = [] - - for (const server of servers) { - const p = getMyUserInformation(server.url, server.accessToken) - .then(res => server.videoChannel = (res.body as User).videoChannels[0]) - - tasks.push(p) - } - - return Promise.all(tasks) -} - -// --------------------------------------------------------------------------- - -export { - updateVideoChannelAvatar, - getVideoChannelsList, - getAccountVideoChannelsList, - addVideoChannel, - updateVideoChannel, - deleteVideoChannel, - getVideoChannel, - setDefaultVideoChannel -} diff --git a/shared/utils/videos/video-comments.ts b/shared/utils/videos/video-comments.ts deleted file mode 100644 index 0ebf69ced..000000000 --- a/shared/utils/videos/video-comments.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as request from 'supertest' -import { makeDeleteRequest } from '../requests/requests' - -function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) { - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - if (token) req.set('Authorization', 'Bearer ' + token) - - return req.set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) { - const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId - - const req = request(url) - .get(path) - .set('Accept', 'application/json') - - if (token) req.set('Authorization', 'Bearer ' + token) - - return req.expect(200) - .expect('Content-Type', /json/) -} - -function addVideoCommentThread (url: string, token: string, videoId: number | string, text: string, expectedStatus = 200) { - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - return request(url) - .post(path) - .send({ text }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function addVideoCommentReply ( - url: string, - token: string, - videoId: number | string, - inReplyToCommentId: number, - text: string, - expectedStatus = 200 -) { - const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId - - return request(url) - .post(path) - .send({ text }) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -function deleteVideoComment ( - url: string, - token: string, - videoId: number | string, - commentId: number, - statusCodeExpected = 204 -) { - const path = '/api/v1/videos/' + videoId + '/comments/' + commentId - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -// --------------------------------------------------------------------------- - -export { - getVideoCommentThreads, - getVideoThreadComments, - addVideoCommentThread, - addVideoCommentReply, - deleteVideoComment -} diff --git a/shared/utils/videos/video-history.ts b/shared/utils/videos/video-history.ts deleted file mode 100644 index dc7095b4d..000000000 --- a/shared/utils/videos/video-history.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' - -function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number, statusCodeExpected = 204) { - const path = '/api/v1/videos/' + videoId + '/watching' - const fields = { currentTime } - - return makePutBodyRequest({ url, path, token, fields, statusCodeExpected }) -} - -function listMyVideosHistory (url: string, token: string) { - const path = '/api/v1/users/me/history/videos' - - return makeGetRequest({ - url, - path, - token, - statusCodeExpected: 200 - }) -} - -function removeMyVideosHistory (url: string, token: string, beforeDate?: string) { - const path = '/api/v1/users/me/history/videos/remove' - - return makePostBodyRequest({ - url, - path, - token, - fields: beforeDate ? { beforeDate } : {}, - statusCodeExpected: 204 - }) -} - -// --------------------------------------------------------------------------- - -export { - userWatchVideo, - listMyVideosHistory, - removeMyVideosHistory -} diff --git a/shared/utils/videos/video-imports.ts b/shared/utils/videos/video-imports.ts deleted file mode 100644 index ec77cdcda..000000000 --- a/shared/utils/videos/video-imports.ts +++ /dev/null @@ -1,57 +0,0 @@ - -import { VideoImportCreate } from '../../models/videos' -import { makeGetRequest, makeUploadRequest } from '../requests/requests' - -function getYoutubeVideoUrl () { - return 'https://youtu.be/msX3jv1XdvM' -} - -function getMagnetURI () { - // tslint:disable:max-line-length - return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' -} - -function getBadVideoUrl () { - return 'https://download.cpy.re/peertube/bad_video.mp4' -} - -function importVideo (url: string, token: string, attributes: VideoImportCreate) { - const path = '/api/v1/videos/imports' - - let attaches: any = {} - if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile } - - return makeUploadRequest({ - url, - path, - token, - attaches, - fields: attributes, - statusCodeExpected: 200 - }) -} - -function getMyVideoImports (url: string, token: string, sort?: string) { - const path = '/api/v1/users/me/videos/imports' - - const query = {} - if (sort) query['sort'] = sort - - return makeGetRequest({ - url, - query, - path, - token, - statusCodeExpected: 200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getBadVideoUrl, - getYoutubeVideoUrl, - importVideo, - getMagnetURI, - getMyVideoImports -} diff --git a/shared/utils/videos/video-playlists.ts b/shared/utils/videos/video-playlists.ts deleted file mode 100644 index 4d110a131..000000000 --- a/shared/utils/videos/video-playlists.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' -import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model' -import { omit } from 'lodash' -import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model' -import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model' -import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model' -import { videoUUIDToId } from './videos' -import { join } from 'path' -import { root } from '..' -import { readdir } from 'fs-extra' -import { expect } from 'chai' -import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model' - -function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) { - const path = '/api/v1/video-playlists' - - const query = { - start, - count, - sort - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: 200 - }) -} - -function getVideoChannelPlaylistsList (url: string, videoChannelName: string, start: number, count: number, sort?: string) { - const path = '/api/v1/video-channels/' + videoChannelName + '/video-playlists' - - const query = { - start, - count, - sort - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: 200 - }) -} - -function getAccountPlaylistsList (url: string, accountName: string, start: number, count: number, sort?: string) { - const path = '/api/v1/accounts/' + accountName + '/video-playlists' - - const query = { - start, - count, - sort - } - - return makeGetRequest({ - url, - path, - query, - statusCodeExpected: 200 - }) -} - -function getAccountPlaylistsListWithToken ( - url: string, - token: string, - accountName: string, - start: number, - count: number, - playlistType?: VideoPlaylistType, - sort?: string -) { - const path = '/api/v1/accounts/' + accountName + '/video-playlists' - - const query = { - start, - count, - playlistType, - sort - } - - return makeGetRequest({ - url, - token, - path, - query, - statusCodeExpected: 200 - }) -} - -function getVideoPlaylist (url: string, playlistId: number | string, statusCodeExpected = 200) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeGetRequest({ - url, - path, - statusCodeExpected - }) -} - -function getVideoPlaylistWithToken (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeGetRequest({ - url, - token, - path, - statusCodeExpected - }) -} - -function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) { - const path = '/api/v1/video-playlists/' + playlistId - - return makeDeleteRequest({ - url, - path, - token, - statusCodeExpected - }) -} - -function createVideoPlaylist (options: { - url: string, - token: string, - playlistAttrs: VideoPlaylistCreate, - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists' - - const fields = omit(options.playlistAttrs, 'thumbnailfile') - - const attaches = options.playlistAttrs.thumbnailfile - ? { thumbnailfile: options.playlistAttrs.thumbnailfile } - : {} - - return makeUploadRequest({ - method: 'POST', - url: options.url, - path, - token: options.token, - fields, - attaches, - statusCodeExpected: options.expectedStatus || 200 - }) -} - -function updateVideoPlaylist (options: { - url: string, - token: string, - playlistAttrs: VideoPlaylistUpdate, - playlistId: number | string, - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId - - const fields = omit(options.playlistAttrs, 'thumbnailfile') - - const attaches = options.playlistAttrs.thumbnailfile - ? { thumbnailfile: options.playlistAttrs.thumbnailfile } - : {} - - return makeUploadRequest({ - method: 'PUT', - url: options.url, - path, - token: options.token, - fields, - attaches, - statusCodeExpected: options.expectedStatus || 204 - }) -} - -async function addVideoInPlaylist (options: { - url: string, - token: string, - playlistId: number | string, - elementAttrs: VideoPlaylistElementCreate | { videoId: string } - expectedStatus?: number -}) { - options.elementAttrs.videoId = await videoUUIDToId(options.url, options.elementAttrs.videoId) - - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' - - return makePostBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || 200 - }) -} - -function updateVideoPlaylistElement (options: { - url: string, - token: string, - playlistId: number | string, - videoId: number | string, - elementAttrs: VideoPlaylistElementUpdate, - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId - - return makePutBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || 204 - }) -} - -function removeVideoFromPlaylist (options: { - url: string, - token: string, - playlistId: number | string, - videoId: number | string, - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId - - return makeDeleteRequest({ - url: options.url, - path, - token: options.token, - statusCodeExpected: options.expectedStatus || 204 - }) -} - -function reorderVideosPlaylist (options: { - url: string, - token: string, - playlistId: number | string, - elementAttrs: { - startPosition: number, - insertAfterPosition: number, - reorderLength?: number - }, - expectedStatus?: number -}) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' - - return makePostBodyRequest({ - url: options.url, - path, - token: options.token, - fields: options.elementAttrs, - statusCodeExpected: options.expectedStatus || 204 - }) -} - -async function checkPlaylistFilesWereRemoved ( - playlistUUID: string, - serverNumber: number, - directories = [ 'thumbnails' ] -) { - const testDirectory = 'test' + serverNumber - - for (const directory of directories) { - const directoryPath = join(root(), testDirectory, directory) - - const files = await readdir(directoryPath) - for (const file of files) { - expect(file).to.not.contain(playlistUUID) - } - } -} - -function getVideoPlaylistPrivacies (url: string) { - const path = '/api/v1/video-playlists/privacies' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function doVideosExistInMyPlaylist (url: string, token: string, videoIds: number[]) { - const path = '/api/v1/users/me/video-playlists/videos-exist' - - return makeGetRequest({ - url, - token, - path, - query: { videoIds }, - statusCodeExpected: 200 - }) -} - -// --------------------------------------------------------------------------- - -export { - getVideoPlaylistPrivacies, - - getVideoPlaylistsList, - getVideoChannelPlaylistsList, - getAccountPlaylistsList, - getAccountPlaylistsListWithToken, - - getVideoPlaylist, - getVideoPlaylistWithToken, - - createVideoPlaylist, - updateVideoPlaylist, - deleteVideoPlaylist, - - addVideoInPlaylist, - updateVideoPlaylistElement, - removeVideoFromPlaylist, - - reorderVideosPlaylist, - - checkPlaylistFilesWereRemoved, - - doVideosExistInMyPlaylist -} diff --git a/shared/utils/videos/video-streaming-playlists.ts b/shared/utils/videos/video-streaming-playlists.ts deleted file mode 100644 index eb25011cb..000000000 --- a/shared/utils/videos/video-streaming-playlists.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { makeRawRequest } from '../requests/requests' -import { sha256 } from '../../../server/helpers/core-utils' -import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' -import { expect } from 'chai' - -function getPlaylist (url: string, statusCodeExpected = 200) { - return makeRawRequest(url, statusCodeExpected) -} - -function getSegment (url: string, statusCodeExpected = 200, range?: string) { - return makeRawRequest(url, statusCodeExpected, range) -} - -function getSegmentSha256 (url: string, statusCodeExpected = 200) { - return makeRawRequest(url, statusCodeExpected) -} - -async function checkSegmentHash ( - baseUrlPlaylist: string, - baseUrlSegment: string, - videoUUID: string, - resolution: number, - hlsPlaylist: VideoStreamingPlaylist -) { - const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`) - const playlist = res.text - - const videoName = `${videoUUID}-${resolution}-fragmented.mp4` - - const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) - - const length = parseInt(matches[1], 10) - const offset = parseInt(matches[2], 10) - const range = `${offset}-${offset + length - 1}` - - const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`) - - const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) - - const sha256Server = resSha.body[ videoName ][range] - expect(sha256(res2.body)).to.equal(sha256Server) -} - -// --------------------------------------------------------------------------- - -export { - getPlaylist, - getSegment, - getSegmentSha256, - checkSegmentHash -} diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts deleted file mode 100644 index b5a07b792..000000000 --- a/shared/utils/videos/videos.ts +++ /dev/null @@ -1,648 +0,0 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai' -import { pathExists, readdir, readFile } from 'fs-extra' -import * as parseTorrent from 'parse-torrent' -import { extname, join } from 'path' -import * as request from 'supertest' -import { - buildAbsoluteFixturePath, - getMyUserInformation, - immutableAssign, - makeGetRequest, - makePutBodyRequest, - makeUploadRequest, - root, - ServerInfo, - testImage -} from '../' -import * as validator from 'validator' -import { VideoDetails, VideoPrivacy } from '../../models/videos' -import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, loadLanguages, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' -import { dateIsValid, webtorrentAdd } from '../miscs/miscs' - -loadLanguages() - -type VideoAttributes = { - name?: string - category?: number - licence?: number - language?: string - nsfw?: boolean - commentsEnabled?: boolean - downloadEnabled?: boolean - waitTranscoding?: boolean - description?: string - originallyPublishedAt?: string - tags?: string[] - channelId?: number - privacy?: VideoPrivacy - fixture?: string - thumbnailfile?: string - previewfile?: string - scheduleUpdate?: { - updateAt: string - privacy?: VideoPrivacy - } -} - -function getVideoCategories (url: string) { - const path = '/api/v1/videos/categories' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getVideoLicences (url: string) { - const path = '/api/v1/videos/licences' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getVideoLanguages (url: string) { - const path = '/api/v1/videos/languages' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getVideoPrivacies (url: string) { - const path = '/api/v1/videos/privacies' - - return makeGetRequest({ - url, - path, - statusCodeExpected: 200 - }) -} - -function getVideo (url: string, id: number | string, expectedStatus = 200) { - const path = '/api/v1/videos/' + id - - return request(url) - .get(path) - .set('Accept', 'application/json') - .expect(expectedStatus) -} - -function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) { - const path = '/api/v1/videos/' + id + '/views' - - const req = request(url) - .post(path) - .set('Accept', 'application/json') - - if (xForwardedFor) { - req.set('X-Forwarded-For', xForwardedFor) - } - - return req.expect(expectedStatus) -} - -function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) { - const path = '/api/v1/videos/' + id - - return request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .set('Accept', 'application/json') - .expect(expectedStatus) -} - -function getVideoDescription (url: string, descriptionPath: string) { - return request(url) - .get(descriptionPath) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getVideosList (url: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: 'name' }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .set('Authorization', 'Bearer ' + token) - .query(immutableAssign(query, { sort: 'name' })) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getLocalVideos (url: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: 'name', filter: 'local' }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) { - const path = '/api/v1/users/me/videos' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - - return req.set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) -} - -function getAccountVideos ( - url: string, - accessToken: string, - accountName: string, - start: number, - count: number, - sort?: string, - query: { nsfw?: boolean } = {} -) { - const path = '/api/v1/accounts/' + accountName + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count, - sort - }), - token: accessToken, - statusCodeExpected: 200 - }) -} - -function getVideoChannelVideos ( - url: string, - accessToken: string, - videoChannelName: string, - start: number, - count: number, - sort?: string, - query: { nsfw?: boolean } = {} -) { - const path = '/api/v1/video-channels/' + videoChannelName + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count, - sort - }), - token: accessToken, - statusCodeExpected: 200 - }) -} - -function getPlaylistVideos ( - url: string, - accessToken: string, - playlistId: number | string, - start: number, - count: number, - query: { nsfw?: boolean } = {} -) { - const path = '/api/v1/video-playlists/' + playlistId + '/videos' - - return makeGetRequest({ - url, - path, - query: immutableAssign(query, { - start, - count - }), - token: accessToken, - statusCodeExpected: 200 - }) -} - -function getVideosListPagination (url: string, start: number, count: number, sort?: string) { - const path = '/api/v1/videos' - - const req = request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - - if (sort) req.query({ sort }) - - return req.set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getVideosListSort (url: string, sort: string) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query({ sort: sort }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) { - const path = '/api/v1/videos' - - return request(url) - .get(path) - .query(query) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) -} - -function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) { - const path = '/api/v1/videos' - - return request(url) - .delete(path + '/' + id) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) -} - -async function checkVideoFilesWereRemoved ( - videoUUID: string, - serverNumber: number, - directories = [ - 'redundancy', - 'videos', - 'thumbnails', - 'torrents', - 'previews', - 'captions', - join('playlists', 'hls'), - join('redundancy', 'hls') - ] -) { - const testDirectory = 'test' + serverNumber - - for (const directory of directories) { - const directoryPath = join(root(), testDirectory, directory) - - const directoryExists = await pathExists(directoryPath) - if (directoryExists === false) continue - - const files = await readdir(directoryPath) - for (const file of files) { - expect(file).to.not.contain(videoUUID) - } - } -} - -async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) { - const path = '/api/v1/videos/upload' - let defaultChannelId = '1' - - try { - const res = await getMyUserInformation(url, accessToken) - defaultChannelId = res.body.videoChannels[0].id - } catch (e) { /* empty */ } - - // Override default attributes - const attributes = Object.assign({ - name: 'my super video', - category: 5, - licence: 4, - language: 'zh', - channelId: defaultChannelId, - nsfw: true, - waitTranscoding: false, - description: 'my super description', - support: 'my super support text', - tags: [ 'tag' ], - privacy: VideoPrivacy.PUBLIC, - commentsEnabled: true, - downloadEnabled: true, - fixture: 'video_short.webm' - }, videoAttributesArg) - - const req = request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .field('name', attributes.name) - .field('nsfw', JSON.stringify(attributes.nsfw)) - .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) - .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled)) - .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding)) - .field('privacy', attributes.privacy.toString()) - .field('channelId', attributes.channelId) - - if (attributes.description !== undefined) { - req.field('description', attributes.description) - } - if (attributes.language !== undefined) { - req.field('language', attributes.language.toString()) - } - if (attributes.category !== undefined) { - req.field('category', attributes.category.toString()) - } - if (attributes.licence !== undefined) { - req.field('licence', attributes.licence.toString()) - } - - for (let i = 0; i < attributes.tags.length; i++) { - req.field('tags[' + i + ']', attributes.tags[i]) - } - - if (attributes.thumbnailfile !== undefined) { - req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile)) - } - if (attributes.previewfile !== undefined) { - req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile)) - } - - if (attributes.scheduleUpdate) { - req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt) - - if (attributes.scheduleUpdate.privacy) { - req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy) - } - } - - if (attributes.originallyPublishedAt !== undefined) { - req.field('originallyPublishedAt', attributes.originallyPublishedAt) - } - - return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) - .expect(specialStatus) -} - -function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) { - const path = '/api/v1/videos/' + id - const body = {} - - if (attributes.name) body['name'] = attributes.name - if (attributes.category) body['category'] = attributes.category - if (attributes.licence) body['licence'] = attributes.licence - if (attributes.language) body['language'] = attributes.language - if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw) - if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled) - if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled) - if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt - if (attributes.description) body['description'] = attributes.description - if (attributes.tags) body['tags'] = attributes.tags - if (attributes.privacy) body['privacy'] = attributes.privacy - if (attributes.channelId) body['channelId'] = attributes.channelId - if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate - - // Upload request - if (attributes.thumbnailfile || attributes.previewfile) { - const attaches: any = {} - if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile - if (attributes.previewfile) attaches.previewfile = attributes.previewfile - - return makeUploadRequest({ - url, - method: 'PUT', - path, - token: accessToken, - fields: body, - attaches, - statusCodeExpected - }) - } - - return makePutBodyRequest({ - url, - path, - fields: body, - token: accessToken, - statusCodeExpected - }) -} - -function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) { - const path = '/api/v1/videos/' + id + '/rate' - - return request(url) - .put(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ rating }) - .expect(specialStatus) -} - -function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) { - return new Promise((res, rej) => { - const torrentName = videoUUID + '-' + resolution + '.torrent' - const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName) - readFile(torrentPath, (err, data) => { - if (err) return rej(err) - - return res(parseTorrent(data)) - }) - }) -} - -async function completeVideoCheck ( - url: string, - video: any, - attributes: { - name: string - category: number - licence: number - language: string - nsfw: boolean - commentsEnabled: boolean - downloadEnabled: boolean - description: string - publishedAt?: string - support: string - originallyPublishedAt?: string, - account: { - name: string - host: string - } - isLocal: boolean - tags: string[] - privacy: number - likes?: number - dislikes?: number - duration: number - channel: { - displayName: string - name: string - description - isLocal: boolean - } - fixture: string - files: { - resolution: number - size: number - }[], - thumbnailfile?: string - previewfile?: string - } -) { - if (!attributes.likes) attributes.likes = 0 - if (!attributes.dislikes) attributes.dislikes = 0 - - expect(video.name).to.equal(attributes.name) - expect(video.category.id).to.equal(attributes.category) - expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc') - expect(video.licence.id).to.equal(attributes.licence) - expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown') - expect(video.language.id).to.equal(attributes.language) - expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown') - expect(video.privacy.id).to.deep.equal(attributes.privacy) - expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy]) - expect(video.nsfw).to.equal(attributes.nsfw) - expect(video.description).to.equal(attributes.description) - expect(video.account.id).to.be.a('number') - expect(video.account.uuid).to.be.a('string') - expect(video.account.host).to.equal(attributes.account.host) - expect(video.account.name).to.equal(attributes.account.name) - expect(video.channel.displayName).to.equal(attributes.channel.displayName) - expect(video.channel.name).to.equal(attributes.channel.name) - expect(video.likes).to.equal(attributes.likes) - expect(video.dislikes).to.equal(attributes.dislikes) - expect(video.isLocal).to.equal(attributes.isLocal) - expect(video.duration).to.equal(attributes.duration) - expect(dateIsValid(video.createdAt)).to.be.true - expect(dateIsValid(video.publishedAt)).to.be.true - expect(dateIsValid(video.updatedAt)).to.be.true - - if (attributes.publishedAt) { - expect(video.publishedAt).to.equal(attributes.publishedAt) - } - - if (attributes.originallyPublishedAt) { - expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt) - } else { - expect(video.originallyPublishedAt).to.be.null - } - - const res = await getVideo(url, video.uuid) - const videoDetails: VideoDetails = res.body - - expect(videoDetails.files).to.have.lengthOf(attributes.files.length) - expect(videoDetails.tags).to.deep.equal(attributes.tags) - expect(videoDetails.account.name).to.equal(attributes.account.name) - expect(videoDetails.account.host).to.equal(attributes.account.host) - expect(video.channel.displayName).to.equal(attributes.channel.displayName) - expect(video.channel.name).to.equal(attributes.channel.name) - expect(videoDetails.channel.host).to.equal(attributes.account.host) - expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal) - expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true - expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true - expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled) - expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled) - - for (const attributeFile of attributes.files) { - const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution) - expect(file).not.to.be.undefined - - let extension = extname(attributes.fixture) - // Transcoding enabled on server 2, extension will always be .mp4 - if (attributes.account.host === 'localhost:9002') extension = '.mp4' - - const magnetUri = file.magnetUri - expect(file.magnetUri).to.have.lengthOf.above(2) - expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) - expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`) - expect(file.resolution.id).to.equal(attributeFile.resolution) - expect(file.resolution.label).to.equal(attributeFile.resolution + 'p') - - const minSize = attributeFile.size - ((10 * attributeFile.size) / 100) - const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) - expect(file.size, - 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')') - .to.be.above(minSize).and.below(maxSize) - - { - await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) - } - - if (attributes.previewfile) { - await testImage(url, attributes.previewfile, videoDetails.previewPath) - } - - const torrent = await webtorrentAdd(magnetUri, true) - expect(torrent.files).to.be.an('array') - expect(torrent.files.length).to.equal(1) - expect(torrent.files[0].path).to.exist.and.to.not.equal('') - } -} - -async function videoUUIDToId (url: string, id: number | string) { - if (validator.isUUID('' + id) === false) return id - - const res = await getVideo(url, id) - return res.body.id -} - -async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) { - const videoAttrs: any = { name: options.videoName } - if (options.nsfw) videoAttrs.nsfw = options.nsfw - - const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) - - return { id: res.body.video.id, uuid: res.body.video.uuid } -} - -// --------------------------------------------------------------------------- - -export { - getVideoDescription, - getVideoCategories, - getVideoLicences, - videoUUIDToId, - getVideoPrivacies, - getVideoLanguages, - getMyVideos, - getAccountVideos, - getVideoChannelVideos, - getVideo, - getVideoWithToken, - getVideosList, - getVideosListPagination, - getVideosListSort, - removeVideo, - getVideosListWithToken, - uploadVideo, - getVideosWithFilters, - updateVideo, - rateVideo, - viewVideo, - parseTorrentVideo, - getLocalVideos, - completeVideoCheck, - checkVideoFilesWereRemoved, - getPlaylistVideos, - uploadVideoAndGetId -} -- cgit v1.2.3