aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/utils/cli/cli.ts24
-rw-r--r--shared/utils/feeds/feeds.ts32
-rw-r--r--shared/utils/index.ts23
-rw-r--r--shared/utils/miscs/email.ts25
-rw-r--r--shared/utils/miscs/miscs.ts101
-rw-r--r--shared/utils/miscs/sql.ts38
-rw-r--r--shared/utils/miscs/stubs.ts14
-rw-r--r--shared/utils/overviews/overviews.ts18
-rw-r--r--shared/utils/requests/activitypub.ts43
-rw-r--r--shared/utils/requests/check-api-params.ts40
-rw-r--r--shared/utils/requests/requests.ts168
-rw-r--r--shared/utils/search/video-channels.ts22
-rw-r--r--shared/utils/search/videos.ts77
-rw-r--r--shared/utils/server/activitypub.ts14
-rw-r--r--shared/utils/server/clients.ts19
-rw-r--r--shared/utils/server/config.ts135
-rw-r--r--shared/utils/server/follows.ts79
-rw-r--r--shared/utils/server/jobs.ts78
-rw-r--r--shared/utils/server/redundancy.ts17
-rw-r--r--shared/utils/server/servers.ts185
-rw-r--r--shared/utils/server/stats.ts22
-rw-r--r--shared/utils/users/accounts.ts63
-rw-r--r--shared/utils/users/blocklist.ts197
-rw-r--r--shared/utils/users/login.ts62
-rw-r--r--shared/utils/users/user-subscriptions.ts82
-rw-r--r--shared/utils/users/users.ts298
-rw-r--r--shared/utils/videos/services.ts23
-rw-r--r--shared/utils/videos/video-abuses.ts65
-rw-r--r--shared/utils/videos/video-blacklist.ts67
-rw-r--r--shared/utils/videos/video-captions.ts71
-rw-r--r--shared/utils/videos/video-change-ownership.ts54
-rw-r--r--shared/utils/videos/video-channels.ts118
-rw-r--r--shared/utils/videos/video-comments.ts87
-rw-r--r--shared/utils/videos/video-history.ts14
-rw-r--r--shared/utils/videos/video-imports.ts52
-rw-r--r--shared/utils/videos/videos.ts577
36 files changed, 3004 insertions, 0 deletions
diff --git a/shared/utils/cli/cli.ts b/shared/utils/cli/cli.ts
new file mode 100644
index 000000000..54d05e9c6
--- /dev/null
+++ b/shared/utils/cli/cli.ts
@@ -0,0 +1,24 @@
1import { exec } from 'child_process'
2
3import { ServerInfo } from '../server/servers'
4
5function getEnvCli (server?: ServerInfo) {
6 return `NODE_ENV=test NODE_APP_INSTANCE=${server.serverNumber}`
7}
8
9async function execCLI (command: string) {
10 return new Promise<string>((res, rej) => {
11 exec(command, (err, stdout, stderr) => {
12 if (err) return rej(err)
13
14 return res(stdout)
15 })
16 })
17}
18
19// ---------------------------------------------------------------------------
20
21export {
22 execCLI,
23 getEnvCli
24}
diff --git a/shared/utils/feeds/feeds.ts b/shared/utils/feeds/feeds.ts
new file mode 100644
index 000000000..af6df2b20
--- /dev/null
+++ b/shared/utils/feeds/feeds.ts
@@ -0,0 +1,32 @@
1import * as request from 'supertest'
2
3type FeedType = 'videos' | 'video-comments'
4
5function getXMLfeed (url: string, feed: FeedType, format?: string) {
6 const path = '/feeds/' + feed + '.xml'
7
8 return request(url)
9 .get(path)
10 .query((format) ? { format: format } : {})
11 .set('Accept', 'application/xml')
12 .expect(200)
13 .expect('Content-Type', /xml/)
14}
15
16function getJSONfeed (url: string, feed: FeedType, query: any = {}) {
17 const path = '/feeds/' + feed + '.json'
18
19 return request(url)
20 .get(path)
21 .query(query)
22 .set('Accept', 'application/json')
23 .expect(200)
24 .expect('Content-Type', /json/)
25}
26
27// ---------------------------------------------------------------------------
28
29export {
30 getXMLfeed,
31 getJSONfeed
32}
diff --git a/shared/utils/index.ts b/shared/utils/index.ts
new file mode 100644
index 000000000..e08bbfd2a
--- /dev/null
+++ b/shared/utils/index.ts
@@ -0,0 +1,23 @@
1export * from './server/activitypub'
2export * from './cli/cli'
3export * from './server/clients'
4export * from './server/config'
5export * from './server/jobs'
6export * from './users/login'
7export * from './miscs/miscs'
8export * from './miscs/stubs'
9export * from './miscs/sql'
10export * from './server/follows'
11export * from './requests/activitypub'
12export * from './requests/requests'
13export * from './requests/check-api-params'
14export * from './server/servers'
15export * from './videos/services'
16export * from './users/users'
17export * from './videos/video-abuses'
18export * from './videos/video-blacklist'
19export * from './videos/video-channels'
20export * from './videos/videos'
21export * from './videos/video-change-ownership'
22export * from './feeds/feeds'
23export * from './search/videos'
diff --git a/shared/utils/miscs/email.ts b/shared/utils/miscs/email.ts
new file mode 100644
index 000000000..21accd09d
--- /dev/null
+++ b/shared/utils/miscs/email.ts
@@ -0,0 +1,25 @@
1import * as MailDev from 'maildev'
2
3function mockSmtpServer (emailsCollection: object[]) {
4 const maildev = new MailDev({
5 ip: '127.0.0.1',
6 smtp: 1025,
7 disableWeb: true,
8 silent: true
9 })
10 maildev.on('new', email => emailsCollection.push(email))
11
12 return new Promise((res, rej) => {
13 maildev.listen(err => {
14 if (err) return rej(err)
15
16 return res()
17 })
18 })
19}
20
21// ---------------------------------------------------------------------------
22
23export {
24 mockSmtpServer
25}
diff --git a/shared/utils/miscs/miscs.ts b/shared/utils/miscs/miscs.ts
new file mode 100644
index 000000000..91a93b631
--- /dev/null
+++ b/shared/utils/miscs/miscs.ts
@@ -0,0 +1,101 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import { isAbsolute, join } from 'path'
5import * as request from 'supertest'
6import * as WebTorrent from 'webtorrent'
7import { pathExists, readFile } from 'fs-extra'
8import * as ffmpeg from 'fluent-ffmpeg'
9
10const expect = chai.expect
11let webtorrent = new WebTorrent()
12
13function immutableAssign <T, U> (target: T, source: U) {
14 return Object.assign<{}, T, U>({}, target, source)
15}
16
17 // Default interval -> 5 minutes
18function dateIsValid (dateString: string, interval = 300000) {
19 const dateToCheck = new Date(dateString)
20 const now = new Date()
21
22 return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval
23}
24
25function wait (milliseconds: number) {
26 return new Promise(resolve => setTimeout(resolve, milliseconds))
27}
28
29function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
30 if (refreshWebTorrent === true) webtorrent = new WebTorrent()
31
32 return new Promise<WebTorrent.Torrent>(res => webtorrent.add(torrent, res))
33}
34
35function root () {
36 // We are in /shared/utils/miscs
37 return join(__dirname, '..', '..', '..')
38}
39
40async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
41 const res = await request(url)
42 .get(imagePath)
43 .expect(200)
44
45 const body = res.body
46
47 const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension))
48 const minLength = body.length - ((20 * body.length) / 100)
49 const maxLength = body.length + ((20 * body.length) / 100)
50
51 expect(data.length).to.be.above(minLength)
52 expect(data.length).to.be.below(maxLength)
53}
54
55function buildAbsoluteFixturePath (path: string, customTravisPath = false) {
56 if (isAbsolute(path)) {
57 return path
58 }
59
60 if (customTravisPath && process.env.TRAVIS) return join(process.env.HOME, 'fixtures', path)
61
62 return join(root(), 'server', 'tests', 'fixtures', path)
63}
64
65async function generateHighBitrateVideo () {
66 const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true)
67
68 const exists = await pathExists(tempFixturePath)
69 if (!exists) {
70
71 // Generate a random, high bitrate video on the fly, so we don't have to include
72 // a large file in the repo. The video needs to have a certain minimum length so
73 // that FFmpeg properly applies bitrate limits.
74 // https://stackoverflow.com/a/15795112
75 return new Promise<string>(async (res, rej) => {
76 ffmpeg()
77 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
78 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
79 .outputOptions([ '-maxrate 10M', '-bufsize 10M' ])
80 .output(tempFixturePath)
81 .on('error', rej)
82 .on('end', () => res(tempFixturePath))
83 .run()
84 })
85 }
86
87 return tempFixturePath
88}
89
90// ---------------------------------------------------------------------------
91
92export {
93 dateIsValid,
94 wait,
95 webtorrentAdd,
96 immutableAssign,
97 testImage,
98 buildAbsoluteFixturePath,
99 root,
100 generateHighBitrateVideo
101}
diff --git a/shared/utils/miscs/sql.ts b/shared/utils/miscs/sql.ts
new file mode 100644
index 000000000..027f78131
--- /dev/null
+++ b/shared/utils/miscs/sql.ts
@@ -0,0 +1,38 @@
1import * as Sequelize from 'sequelize'
2
3function getSequelize (serverNumber: number) {
4 const dbname = 'peertube_test' + serverNumber
5 const username = 'peertube'
6 const password = 'peertube'
7 const host = 'localhost'
8 const port = 5432
9
10 return new Sequelize(dbname, username, password, {
11 dialect: 'postgres',
12 host,
13 port,
14 operatorsAliases: false,
15 logging: false
16 })
17}
18
19function setActorField (serverNumber: number, to: string, field: string, value: string) {
20 const seq = getSequelize(serverNumber)
21
22 const options = { type: Sequelize.QueryTypes.UPDATE }
23
24 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
25}
26
27function setVideoField (serverNumber: number, uuid: string, field: string, value: string) {
28 const seq = getSequelize(serverNumber)
29
30 const options = { type: Sequelize.QueryTypes.UPDATE }
31
32 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
33}
34
35export {
36 setVideoField,
37 setActorField
38}
diff --git a/shared/utils/miscs/stubs.ts b/shared/utils/miscs/stubs.ts
new file mode 100644
index 000000000..d1eb0e3b2
--- /dev/null
+++ b/shared/utils/miscs/stubs.ts
@@ -0,0 +1,14 @@
1function buildRequestStub (): any {
2 return { }
3}
4
5function buildResponseStub (): any {
6 return {
7 locals: {}
8 }
9}
10
11export {
12 buildResponseStub,
13 buildRequestStub
14}
diff --git a/shared/utils/overviews/overviews.ts b/shared/utils/overviews/overviews.ts
new file mode 100644
index 000000000..23e3ceb1e
--- /dev/null
+++ b/shared/utils/overviews/overviews.ts
@@ -0,0 +1,18 @@
1import { makeGetRequest } from '../requests/requests'
2
3function getVideosOverview (url: string, useCache = false) {
4 const path = '/api/v1/overviews/videos'
5
6 const query = {
7 t: useCache ? undefined : new Date().getTime()
8 }
9
10 return makeGetRequest({
11 url,
12 path,
13 query,
14 statusCodeExpected: 200
15 })
16}
17
18export { getVideosOverview }
diff --git a/shared/utils/requests/activitypub.ts b/shared/utils/requests/activitypub.ts
new file mode 100644
index 000000000..e2348ace0
--- /dev/null
+++ b/shared/utils/requests/activitypub.ts
@@ -0,0 +1,43 @@
1import { doRequest } from '../../../server/helpers/requests'
2import { HTTP_SIGNATURE } from '../../../server/initializers'
3import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils'
4import { activityPubContextify } from '../../../server/helpers/activitypub'
5
6function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
7 const options = {
8 method: 'POST',
9 uri: url,
10 json: body,
11 httpSignature,
12 headers
13 }
14
15 return doRequest(options)
16}
17
18async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
19 const follow = {
20 type: 'Follow',
21 id: by.url + '/toto',
22 actor: by.url,
23 object: to.url
24 }
25
26 const body = activityPubContextify(follow)
27
28 const httpSignature = {
29 algorithm: HTTP_SIGNATURE.ALGORITHM,
30 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
31 keyId: by.url,
32 key: by.privateKey,
33 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
34 }
35 const headers = buildGlobalHeaders(body)
36
37 return makePOSTAPRequest(to.url, body, httpSignature, headers)
38}
39
40export {
41 makePOSTAPRequest,
42 makeFollowRequest
43}
diff --git a/shared/utils/requests/check-api-params.ts b/shared/utils/requests/check-api-params.ts
new file mode 100644
index 000000000..a2a549682
--- /dev/null
+++ b/shared/utils/requests/check-api-params.ts
@@ -0,0 +1,40 @@
1import { makeGetRequest } from './requests'
2import { immutableAssign } from '../miscs/miscs'
3
4function checkBadStartPagination (url: string, path: string, token?: string, query = {}) {
5 return makeGetRequest({
6 url,
7 path,
8 token,
9 query: immutableAssign(query, { start: 'hello' }),
10 statusCodeExpected: 400
11 })
12}
13
14function checkBadCountPagination (url: string, path: string, token?: string, query = {}) {
15 return makeGetRequest({
16 url,
17 path,
18 token,
19 query: immutableAssign(query, { count: 'hello' }),
20 statusCodeExpected: 400
21 })
22}
23
24function checkBadSortPagination (url: string, path: string, token?: string, query = {}) {
25 return makeGetRequest({
26 url,
27 path,
28 token,
29 query: immutableAssign(query, { sort: 'hello' }),
30 statusCodeExpected: 400
31 })
32}
33
34// ---------------------------------------------------------------------------
35
36export {
37 checkBadStartPagination,
38 checkBadCountPagination,
39 checkBadSortPagination
40}
diff --git a/shared/utils/requests/requests.ts b/shared/utils/requests/requests.ts
new file mode 100644
index 000000000..77e9f6164
--- /dev/null
+++ b/shared/utils/requests/requests.ts
@@ -0,0 +1,168 @@
1import * as request from 'supertest'
2import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
3import { isAbsolute, join } from 'path'
4
5function makeGetRequest (options: {
6 url: string,
7 path: string,
8 query?: any,
9 token?: string,
10 statusCodeExpected?: number,
11 contentType?: string
12}) {
13 if (!options.statusCodeExpected) options.statusCodeExpected = 400
14 if (options.contentType === undefined) options.contentType = 'application/json'
15
16 const req = request(options.url)
17 .get(options.path)
18
19 if (options.contentType) req.set('Accept', options.contentType)
20 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
21 if (options.query) req.query(options.query)
22
23 return req.expect(options.statusCodeExpected)
24}
25
26function makeDeleteRequest (options: {
27 url: string,
28 path: string,
29 token?: string,
30 statusCodeExpected?: number
31}) {
32 if (!options.statusCodeExpected) options.statusCodeExpected = 400
33
34 const req = request(options.url)
35 .delete(options.path)
36 .set('Accept', 'application/json')
37
38 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
39
40 return req.expect(options.statusCodeExpected)
41}
42
43function makeUploadRequest (options: {
44 url: string,
45 method?: 'POST' | 'PUT',
46 path: string,
47 token?: string,
48 fields: { [ fieldName: string ]: any },
49 attaches: { [ attachName: string ]: any | any[] },
50 statusCodeExpected?: number
51}) {
52 if (!options.statusCodeExpected) options.statusCodeExpected = 400
53
54 let req: request.Test
55 if (options.method === 'PUT') {
56 req = request(options.url).put(options.path)
57 } else {
58 req = request(options.url).post(options.path)
59 }
60
61 req.set('Accept', 'application/json')
62
63 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
64
65 Object.keys(options.fields).forEach(field => {
66 const value = options.fields[field]
67
68 if (Array.isArray(value)) {
69 for (let i = 0; i < value.length; i++) {
70 req.field(field + '[' + i + ']', value[i])
71 }
72 } else {
73 req.field(field, value)
74 }
75 })
76
77 Object.keys(options.attaches).forEach(attach => {
78 const value = options.attaches[attach]
79 if (Array.isArray(value)) {
80 req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1])
81 } else {
82 req.attach(attach, buildAbsoluteFixturePath(value))
83 }
84 })
85
86 return req.expect(options.statusCodeExpected)
87}
88
89function makePostBodyRequest (options: {
90 url: string,
91 path: string,
92 token?: string,
93 fields?: { [ fieldName: string ]: any },
94 statusCodeExpected?: number
95}) {
96 if (!options.fields) options.fields = {}
97 if (!options.statusCodeExpected) options.statusCodeExpected = 400
98
99 const req = request(options.url)
100 .post(options.path)
101 .set('Accept', 'application/json')
102
103 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
104
105 return req.send(options.fields)
106 .expect(options.statusCodeExpected)
107}
108
109function makePutBodyRequest (options: {
110 url: string,
111 path: string,
112 token?: string,
113 fields: { [ fieldName: string ]: any },
114 statusCodeExpected?: number
115}) {
116 if (!options.statusCodeExpected) options.statusCodeExpected = 400
117
118 const req = request(options.url)
119 .put(options.path)
120 .set('Accept', 'application/json')
121
122 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
123
124 return req.send(options.fields)
125 .expect(options.statusCodeExpected)
126}
127
128function makeHTMLRequest (url: string, path: string) {
129 return request(url)
130 .get(path)
131 .set('Accept', 'text/html')
132 .expect(200)
133}
134
135function updateAvatarRequest (options: {
136 url: string,
137 path: string,
138 accessToken: string,
139 fixture: string
140}) {
141 let filePath = ''
142 if (isAbsolute(options.fixture)) {
143 filePath = options.fixture
144 } else {
145 filePath = join(root(), 'server', 'tests', 'fixtures', options.fixture)
146 }
147
148 return makeUploadRequest({
149 url: options.url,
150 path: options.path,
151 token: options.accessToken,
152 fields: {},
153 attaches: { avatarfile: filePath },
154 statusCodeExpected: 200
155 })
156}
157
158// ---------------------------------------------------------------------------
159
160export {
161 makeHTMLRequest,
162 makeGetRequest,
163 makeUploadRequest,
164 makePostBodyRequest,
165 makePutBodyRequest,
166 makeDeleteRequest,
167 updateAvatarRequest
168}
diff --git a/shared/utils/search/video-channels.ts b/shared/utils/search/video-channels.ts
new file mode 100644
index 000000000..0532134ae
--- /dev/null
+++ b/shared/utils/search/video-channels.ts
@@ -0,0 +1,22 @@
1import { makeGetRequest } from '../requests/requests'
2
3function searchVideoChannel (url: string, search: string, token?: string, statusCodeExpected = 200) {
4 const path = '/api/v1/search/video-channels'
5
6 return makeGetRequest({
7 url,
8 path,
9 query: {
10 sort: '-createdAt',
11 search
12 },
13 token,
14 statusCodeExpected
15 })
16}
17
18// ---------------------------------------------------------------------------
19
20export {
21 searchVideoChannel
22}
diff --git a/shared/utils/search/videos.ts b/shared/utils/search/videos.ts
new file mode 100644
index 000000000..ba4627017
--- /dev/null
+++ b/shared/utils/search/videos.ts
@@ -0,0 +1,77 @@
1/* tslint:disable:no-unused-expression */
2
3import * as request from 'supertest'
4import { VideosSearchQuery } from '../../models/search'
5import { immutableAssign } from '../miscs/miscs'
6
7function searchVideo (url: string, search: string) {
8 const path = '/api/v1/search/videos'
9 const req = request(url)
10 .get(path)
11 .query({ sort: '-publishedAt', search })
12 .set('Accept', 'application/json')
13
14 return req.expect(200)
15 .expect('Content-Type', /json/)
16}
17
18function searchVideoWithToken (url: string, search: string, token: string, query: { nsfw?: boolean } = {}) {
19 const path = '/api/v1/search/videos'
20 const req = request(url)
21 .get(path)
22 .set('Authorization', 'Bearer ' + token)
23 .query(immutableAssign(query, { sort: '-publishedAt', search }))
24 .set('Accept', 'application/json')
25
26 return req.expect(200)
27 .expect('Content-Type', /json/)
28}
29
30function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
31 const path = '/api/v1/search/videos'
32
33 const req = request(url)
34 .get(path)
35 .query({ start })
36 .query({ search })
37 .query({ count })
38
39 if (sort) req.query({ sort })
40
41 return req.set('Accept', 'application/json')
42 .expect(200)
43 .expect('Content-Type', /json/)
44}
45
46function searchVideoWithSort (url: string, search: string, sort: string) {
47 const path = '/api/v1/search/videos'
48
49 return request(url)
50 .get(path)
51 .query({ search })
52 .query({ sort })
53 .set('Accept', 'application/json')
54 .expect(200)
55 .expect('Content-Type', /json/)
56}
57
58function advancedVideosSearch (url: string, options: VideosSearchQuery) {
59 const path = '/api/v1/search/videos'
60
61 return request(url)
62 .get(path)
63 .query(options)
64 .set('Accept', 'application/json')
65 .expect(200)
66 .expect('Content-Type', /json/)
67}
68
69// ---------------------------------------------------------------------------
70
71export {
72 searchVideo,
73 advancedVideosSearch,
74 searchVideoWithToken,
75 searchVideoWithPagination,
76 searchVideoWithSort
77}
diff --git a/shared/utils/server/activitypub.ts b/shared/utils/server/activitypub.ts
new file mode 100644
index 000000000..eccb198ca
--- /dev/null
+++ b/shared/utils/server/activitypub.ts
@@ -0,0 +1,14 @@
1import * as request from 'supertest'
2
3function makeActivityPubGetRequest (url: string, path: string, expectedStatus = 200) {
4 return request(url)
5 .get(path)
6 .set('Accept', 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8')
7 .expect(expectedStatus)
8}
9
10// ---------------------------------------------------------------------------
11
12export {
13 makeActivityPubGetRequest
14}
diff --git a/shared/utils/server/clients.ts b/shared/utils/server/clients.ts
new file mode 100644
index 000000000..273aac747
--- /dev/null
+++ b/shared/utils/server/clients.ts
@@ -0,0 +1,19 @@
1import * as request from 'supertest'
2import * as urlUtil from 'url'
3
4function getClient (url: string) {
5 const path = '/api/v1/oauth-clients/local'
6
7 return request(url)
8 .get(path)
9 .set('Host', urlUtil.parse(url).host)
10 .set('Accept', 'application/json')
11 .expect(200)
12 .expect('Content-Type', /json/)
13}
14
15// ---------------------------------------------------------------------------
16
17export {
18 getClient
19}
diff --git a/shared/utils/server/config.ts b/shared/utils/server/config.ts
new file mode 100644
index 000000000..5b888b061
--- /dev/null
+++ b/shared/utils/server/config.ts
@@ -0,0 +1,135 @@
1import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests'
2import { CustomConfig } from '../../models/server/custom-config.model'
3
4function getConfig (url: string) {
5 const path = '/api/v1/config'
6
7 return makeGetRequest({
8 url,
9 path,
10 statusCodeExpected: 200
11 })
12}
13
14function getAbout (url: string) {
15 const path = '/api/v1/config/about'
16
17 return makeGetRequest({
18 url,
19 path,
20 statusCodeExpected: 200
21 })
22}
23
24function getCustomConfig (url: string, token: string, statusCodeExpected = 200) {
25 const path = '/api/v1/config/custom'
26
27 return makeGetRequest({
28 url,
29 token,
30 path,
31 statusCodeExpected
32 })
33}
34
35function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = 200) {
36 const path = '/api/v1/config/custom'
37
38 return makePutBodyRequest({
39 url,
40 token,
41 path,
42 fields: newCustomConfig,
43 statusCodeExpected
44 })
45}
46
47function updateCustomSubConfig (url: string, token: string, newConfig: any) {
48 const updateParams: CustomConfig = {
49 instance: {
50 name: 'PeerTube updated',
51 shortDescription: 'my short description',
52 description: 'my super description',
53 terms: 'my super terms',
54 defaultClientRoute: '/videos/recently-added',
55 defaultNSFWPolicy: 'blur',
56 customizations: {
57 javascript: 'alert("coucou")',
58 css: 'body { background-color: red; }'
59 }
60 },
61 services: {
62 twitter: {
63 username: '@MySuperUsername',
64 whitelisted: true
65 }
66 },
67 cache: {
68 previews: {
69 size: 2
70 },
71 captions: {
72 size: 3
73 }
74 },
75 signup: {
76 enabled: false,
77 limit: 5,
78 requiresEmailVerification: false
79 },
80 admin: {
81 email: 'superadmin1@example.com'
82 },
83 user: {
84 videoQuota: 5242881,
85 videoQuotaDaily: 318742
86 },
87 transcoding: {
88 enabled: true,
89 threads: 1,
90 resolutions: {
91 '240p': false,
92 '360p': true,
93 '480p': true,
94 '720p': false,
95 '1080p': false
96 }
97 },
98 import: {
99 videos: {
100 http: {
101 enabled: false
102 },
103 torrent: {
104 enabled: false
105 }
106 }
107 }
108 }
109
110 Object.assign(updateParams, newConfig)
111
112 return updateCustomConfig(url, token, updateParams)
113}
114
115function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) {
116 const path = '/api/v1/config/custom'
117
118 return makeDeleteRequest({
119 url,
120 token,
121 path,
122 statusCodeExpected
123 })
124}
125
126// ---------------------------------------------------------------------------
127
128export {
129 getConfig,
130 getCustomConfig,
131 updateCustomConfig,
132 getAbout,
133 deleteCustomConfig,
134 updateCustomSubConfig
135}
diff --git a/shared/utils/server/follows.ts b/shared/utils/server/follows.ts
new file mode 100644
index 000000000..7741757a6
--- /dev/null
+++ b/shared/utils/server/follows.ts
@@ -0,0 +1,79 @@
1import * as request from 'supertest'
2import { ServerInfo } from './servers'
3import { waitJobs } from './jobs'
4
5function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
6 const path = '/api/v1/server/followers'
7
8 return request(url)
9 .get(path)
10 .query({ start })
11 .query({ count })
12 .query({ sort })
13 .query({ search })
14 .set('Accept', 'application/json')
15 .expect(200)
16 .expect('Content-Type', /json/)
17}
18
19function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
20 const path = '/api/v1/server/following'
21
22 return request(url)
23 .get(path)
24 .query({ start })
25 .query({ count })
26 .query({ sort })
27 .query({ search })
28 .set('Accept', 'application/json')
29 .expect(200)
30 .expect('Content-Type', /json/)
31}
32
33async function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) {
34 const path = '/api/v1/server/following'
35
36 const followingHosts = following.map(f => f.replace(/^http:\/\//, ''))
37 const res = await request(follower)
38 .post(path)
39 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + accessToken)
41 .send({ 'hosts': followingHosts })
42 .expect(expectedStatus)
43
44 return res
45}
46
47async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
48 const path = '/api/v1/server/following/' + target.host
49
50 const res = await request(url)
51 .delete(path)
52 .set('Accept', 'application/json')
53 .set('Authorization', 'Bearer ' + accessToken)
54 .expect(expectedStatus)
55
56 return res
57}
58
59async function doubleFollow (server1: ServerInfo, server2: ServerInfo) {
60 await Promise.all([
61 follow(server1.url, [ server2.url ], server1.accessToken),
62 follow(server2.url, [ server1.url ], server2.accessToken)
63 ])
64
65 // Wait request propagation
66 await waitJobs([ server1, server2 ])
67
68 return true
69}
70
71// ---------------------------------------------------------------------------
72
73export {
74 getFollowersListPaginationAndSort,
75 getFollowingListPaginationAndSort,
76 unfollow,
77 follow,
78 doubleFollow
79}
diff --git a/shared/utils/server/jobs.ts b/shared/utils/server/jobs.ts
new file mode 100644
index 000000000..7c7e89824
--- /dev/null
+++ b/shared/utils/server/jobs.ts
@@ -0,0 +1,78 @@
1import * as request from 'supertest'
2import { Job, JobState } from '../../models'
3import { wait } from '../miscs/miscs'
4import { ServerInfo } from './servers'
5
6function getJobsList (url: string, accessToken: string, state: JobState) {
7 const path = '/api/v1/jobs/' + state
8
9 return request(url)
10 .get(path)
11 .set('Accept', 'application/json')
12 .set('Authorization', 'Bearer ' + accessToken)
13 .expect(200)
14 .expect('Content-Type', /json/)
15}
16
17function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) {
18 const path = '/api/v1/jobs/' + state
19
20 return request(url)
21 .get(path)
22 .query({ start })
23 .query({ count })
24 .query({ sort })
25 .set('Accept', 'application/json')
26 .set('Authorization', 'Bearer ' + accessToken)
27 .expect(200)
28 .expect('Content-Type', /json/)
29}
30
31async function waitJobs (serversArg: ServerInfo[] | ServerInfo) {
32 let servers: ServerInfo[]
33
34 if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ]
35 else servers = serversArg as ServerInfo[]
36
37 const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
38 const tasks: Promise<any>[] = []
39 let pendingRequests: boolean
40
41 do {
42 pendingRequests = false
43
44 // Check if each server has pending request
45 for (const server of servers) {
46 for (const state of states) {
47 const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt')
48 .then(res => res.body.data)
49 .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views'))
50 .then(jobs => {
51 if (jobs.length !== 0) pendingRequests = true
52 })
53 tasks.push(p)
54 }
55 }
56
57 await Promise.all(tasks)
58
59 // Retry, in case of new jobs were created
60 if (pendingRequests === false) {
61 await wait(1000)
62
63 await Promise.all(tasks)
64 }
65
66 if (pendingRequests) {
67 await wait(1000)
68 }
69 } while (pendingRequests)
70}
71
72// ---------------------------------------------------------------------------
73
74export {
75 getJobsList,
76 waitJobs,
77 getJobsListPaginationAndSort
78}
diff --git a/shared/utils/server/redundancy.ts b/shared/utils/server/redundancy.ts
new file mode 100644
index 000000000..c39ff2c8b
--- /dev/null
+++ b/shared/utils/server/redundancy.ts
@@ -0,0 +1,17 @@
1import { makePutBodyRequest } from '../requests/requests'
2
3async function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) {
4 const path = '/api/v1/server/redundancy/' + host
5
6 return makePutBodyRequest({
7 url,
8 path,
9 token: accessToken,
10 fields: { redundancyAllowed },
11 statusCodeExpected: expectedStatus
12 })
13}
14
15export {
16 updateRedundancy
17}
diff --git a/shared/utils/server/servers.ts b/shared/utils/server/servers.ts
new file mode 100644
index 000000000..88d2b390c
--- /dev/null
+++ b/shared/utils/server/servers.ts
@@ -0,0 +1,185 @@
1import { ChildProcess, exec, fork } from 'child_process'
2import { join } from 'path'
3import { root, wait } from '../miscs/miscs'
4import { readFile } from 'fs-extra'
5
6interface ServerInfo {
7 app: ChildProcess,
8 url: string
9 host: string
10 serverNumber: number
11
12 client: {
13 id: string,
14 secret: string
15 }
16
17 user: {
18 username: string,
19 password: string,
20 email?: string
21 }
22
23 accessToken?: string
24
25 video?: {
26 id: number
27 uuid: string
28 name: string
29 account: {
30 name: string
31 }
32 }
33
34 remoteVideo?: {
35 id: number
36 uuid: string
37 }
38}
39
40function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
41 let apps = []
42 let i = 0
43
44 return new Promise<ServerInfo[]>(res => {
45 function anotherServerDone (serverNumber, app) {
46 apps[serverNumber - 1] = app
47 i++
48 if (i === totalServers) {
49 return res(apps)
50 }
51 }
52
53 flushTests()
54 .then(() => {
55 for (let j = 1; j <= totalServers; j++) {
56 runServer(j, configOverride).then(app => anotherServerDone(j, app))
57 }
58 })
59 })
60}
61
62function flushTests () {
63 return new Promise<void>((res, rej) => {
64 return exec('npm run clean:server:test', err => {
65 if (err) return rej(err)
66
67 return res()
68 })
69 })
70}
71
72function runServer (serverNumber: number, configOverride?: Object, args = []) {
73 const server: ServerInfo = {
74 app: null,
75 serverNumber: serverNumber,
76 url: `http://localhost:${9000 + serverNumber}`,
77 host: `localhost:${9000 + serverNumber}`,
78 client: {
79 id: null,
80 secret: null
81 },
82 user: {
83 username: null,
84 password: null
85 }
86 }
87
88 // These actions are async so we need to be sure that they have both been done
89 const serverRunString = {
90 'Server listening': false
91 }
92 const key = 'Database peertube_test' + serverNumber + ' is ready'
93 serverRunString[key] = false
94
95 const regexps = {
96 client_id: 'Client id: (.+)',
97 client_secret: 'Client secret: (.+)',
98 user_username: 'Username: (.+)',
99 user_password: 'User password: (.+)'
100 }
101
102 // Share the environment
103 const env = Object.create(process.env)
104 env['NODE_ENV'] = 'test'
105 env['NODE_APP_INSTANCE'] = serverNumber.toString()
106
107 if (configOverride !== undefined) {
108 env['NODE_CONFIG'] = JSON.stringify(configOverride)
109 }
110
111 const options = {
112 silent: true,
113 env: env,
114 detached: true
115 }
116
117 return new Promise<ServerInfo>(res => {
118 server.app = fork(join(root(), 'dist', 'server.js'), args, options)
119 server.app.stdout.on('data', function onStdout (data) {
120 let dontContinue = false
121
122 // Capture things if we want to
123 for (const key of Object.keys(regexps)) {
124 const regexp = regexps[key]
125 const matches = data.toString().match(regexp)
126 if (matches !== null) {
127 if (key === 'client_id') server.client.id = matches[1]
128 else if (key === 'client_secret') server.client.secret = matches[1]
129 else if (key === 'user_username') server.user.username = matches[1]
130 else if (key === 'user_password') server.user.password = matches[1]
131 }
132 }
133
134 // Check if all required sentences are here
135 for (const key of Object.keys(serverRunString)) {
136 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
137 if (serverRunString[key] === false) dontContinue = true
138 }
139
140 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
141 if (dontContinue === true) return
142
143 server.app.stdout.removeListener('data', onStdout)
144 res(server)
145 })
146 })
147}
148
149async function reRunServer (server: ServerInfo, configOverride?: any) {
150 const newServer = await runServer(server.serverNumber, configOverride)
151 server.app = newServer.app
152
153 return server
154}
155
156function killallServers (servers: ServerInfo[]) {
157 for (const server of servers) {
158 process.kill(-server.app.pid)
159 }
160}
161
162async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
163 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log')
164
165 while (true) {
166 const buf = await readFile(logfile)
167
168 const matches = buf.toString().match(new RegExp(str, 'g'))
169 if (matches && matches.length === count) return
170
171 await wait(1000)
172 }
173}
174
175// ---------------------------------------------------------------------------
176
177export {
178 ServerInfo,
179 flushAndRunMultipleServers,
180 flushTests,
181 runServer,
182 killallServers,
183 reRunServer,
184 waitUntilLog
185}
diff --git a/shared/utils/server/stats.ts b/shared/utils/server/stats.ts
new file mode 100644
index 000000000..6f079ad18
--- /dev/null
+++ b/shared/utils/server/stats.ts
@@ -0,0 +1,22 @@
1import { makeGetRequest } from '../requests/requests'
2
3function getStats (url: string, useCache = false) {
4 const path = '/api/v1/server/stats'
5
6 const query = {
7 t: useCache ? undefined : new Date().getTime()
8 }
9
10 return makeGetRequest({
11 url,
12 path,
13 query,
14 statusCodeExpected: 200
15 })
16}
17
18// ---------------------------------------------------------------------------
19
20export {
21 getStats
22}
diff --git a/shared/utils/users/accounts.ts b/shared/utils/users/accounts.ts
new file mode 100644
index 000000000..388eb6973
--- /dev/null
+++ b/shared/utils/users/accounts.ts
@@ -0,0 +1,63 @@
1/* tslint:disable:no-unused-expression */
2
3import { expect } from 'chai'
4import { existsSync, readdir } from 'fs-extra'
5import { join } from 'path'
6import { Account } from '../../models/actors'
7import { root } from '../miscs/miscs'
8import { makeGetRequest } from '../requests/requests'
9
10function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) {
11 const path = '/api/v1/accounts'
12
13 return makeGetRequest({
14 url,
15 query: { sort },
16 path,
17 statusCodeExpected
18 })
19}
20
21function getAccount (url: string, accountName: string, statusCodeExpected = 200) {
22 const path = '/api/v1/accounts/' + accountName
23
24 return makeGetRequest({
25 url,
26 path,
27 statusCodeExpected
28 })
29}
30
31async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) {
32 const res = await getAccountsList(url)
33 const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain)
34
35 const message = `${nameWithDomain} on ${url}`
36 expect(account.followersCount).to.equal(followersCount, message)
37 expect(account.followingCount).to.equal(followingCount, message)
38}
39
40async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: number) {
41 const testDirectory = 'test' + serverNumber
42
43 for (const directory of [ 'avatars' ]) {
44 const directoryPath = join(root(), testDirectory, directory)
45
46 const directoryExists = existsSync(directoryPath)
47 expect(directoryExists).to.be.true
48
49 const files = await readdir(directoryPath)
50 for (const file of files) {
51 expect(file).to.not.contain(actorUUID)
52 }
53 }
54}
55
56// ---------------------------------------------------------------------------
57
58export {
59 getAccount,
60 expectAccountFollows,
61 getAccountsList,
62 checkActorFilesWereRemoved
63}
diff --git a/shared/utils/users/blocklist.ts b/shared/utils/users/blocklist.ts
new file mode 100644
index 000000000..5feb84179
--- /dev/null
+++ b/shared/utils/users/blocklist.ts
@@ -0,0 +1,197 @@
1/* tslint:disable:no-unused-expression */
2
3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests'
4
5function getAccountBlocklistByAccount (
6 url: string,
7 token: string,
8 start: number,
9 count: number,
10 sort = '-createdAt',
11 statusCodeExpected = 200
12) {
13 const path = '/api/v1/users/me/blocklist/accounts'
14
15 return makeGetRequest({
16 url,
17 token,
18 query: { start, count, sort },
19 path,
20 statusCodeExpected
21 })
22}
23
24function addAccountToAccountBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) {
25 const path = '/api/v1/users/me/blocklist/accounts'
26
27 return makePostBodyRequest({
28 url,
29 path,
30 token,
31 fields: {
32 accountName: accountToBlock
33 },
34 statusCodeExpected
35 })
36}
37
38function removeAccountFromAccountBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) {
39 const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock
40
41 return makeDeleteRequest({
42 url,
43 path,
44 token,
45 statusCodeExpected
46 })
47}
48
49function getServerBlocklistByAccount (
50 url: string,
51 token: string,
52 start: number,
53 count: number,
54 sort = '-createdAt',
55 statusCodeExpected = 200
56) {
57 const path = '/api/v1/users/me/blocklist/servers'
58
59 return makeGetRequest({
60 url,
61 token,
62 query: { start, count, sort },
63 path,
64 statusCodeExpected
65 })
66}
67
68function addServerToAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
69 const path = '/api/v1/users/me/blocklist/servers'
70
71 return makePostBodyRequest({
72 url,
73 path,
74 token,
75 fields: {
76 host: serverToBlock
77 },
78 statusCodeExpected
79 })
80}
81
82function removeServerFromAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
83 const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock
84
85 return makeDeleteRequest({
86 url,
87 path,
88 token,
89 statusCodeExpected
90 })
91}
92
93function getAccountBlocklistByServer (
94 url: string,
95 token: string,
96 start: number,
97 count: number,
98 sort = '-createdAt',
99 statusCodeExpected = 200
100) {
101 const path = '/api/v1/server/blocklist/accounts'
102
103 return makeGetRequest({
104 url,
105 token,
106 query: { start, count, sort },
107 path,
108 statusCodeExpected
109 })
110}
111
112function addAccountToServerBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) {
113 const path = '/api/v1/server/blocklist/accounts'
114
115 return makePostBodyRequest({
116 url,
117 path,
118 token,
119 fields: {
120 accountName: accountToBlock
121 },
122 statusCodeExpected
123 })
124}
125
126function removeAccountFromServerBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) {
127 const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock
128
129 return makeDeleteRequest({
130 url,
131 path,
132 token,
133 statusCodeExpected
134 })
135}
136
137function getServerBlocklistByServer (
138 url: string,
139 token: string,
140 start: number,
141 count: number,
142 sort = '-createdAt',
143 statusCodeExpected = 200
144) {
145 const path = '/api/v1/server/blocklist/servers'
146
147 return makeGetRequest({
148 url,
149 token,
150 query: { start, count, sort },
151 path,
152 statusCodeExpected
153 })
154}
155
156function addServerToServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
157 const path = '/api/v1/server/blocklist/servers'
158
159 return makePostBodyRequest({
160 url,
161 path,
162 token,
163 fields: {
164 host: serverToBlock
165 },
166 statusCodeExpected
167 })
168}
169
170function removeServerFromServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
171 const path = '/api/v1/server/blocklist/servers/' + serverToBlock
172
173 return makeDeleteRequest({
174 url,
175 path,
176 token,
177 statusCodeExpected
178 })
179}
180
181// ---------------------------------------------------------------------------
182
183export {
184 getAccountBlocklistByAccount,
185 addAccountToAccountBlocklist,
186 removeAccountFromAccountBlocklist,
187 getServerBlocklistByAccount,
188 addServerToAccountBlocklist,
189 removeServerFromAccountBlocklist,
190
191 getAccountBlocklistByServer,
192 addAccountToServerBlocklist,
193 removeAccountFromServerBlocklist,
194 getServerBlocklistByServer,
195 addServerToServerBlocklist,
196 removeServerFromServerBlocklist
197}
diff --git a/shared/utils/users/login.ts b/shared/utils/users/login.ts
new file mode 100644
index 000000000..ddeb9df2a
--- /dev/null
+++ b/shared/utils/users/login.ts
@@ -0,0 +1,62 @@
1import * as request from 'supertest'
2
3import { ServerInfo } from '../server/servers'
4
5type Client = { id: string, secret: string }
6type User = { username: string, password: string }
7type Server = { url: string, client: Client, user: User }
8
9function login (url: string, client: Client, user: User, expectedStatus = 200) {
10 const path = '/api/v1/users/token'
11
12 const body = {
13 client_id: client.id,
14 client_secret: client.secret,
15 username: user.username,
16 password: user.password,
17 response_type: 'code',
18 grant_type: 'password',
19 scope: 'upload'
20 }
21
22 return request(url)
23 .post(path)
24 .type('form')
25 .send(body)
26 .expect(expectedStatus)
27}
28
29async function serverLogin (server: Server) {
30 const res = await login(server.url, server.client, server.user, 200)
31
32 return res.body.access_token as string
33}
34
35async function userLogin (server: Server, user: User, expectedStatus = 200) {
36 const res = await login(server.url, server.client, user, expectedStatus)
37
38 return res.body.access_token as string
39}
40
41function setAccessTokensToServers (servers: ServerInfo[]) {
42 const tasks: Promise<any>[] = []
43
44 for (const server of servers) {
45 const p = serverLogin(server).then(t => server.accessToken = t)
46 tasks.push(p)
47 }
48
49 return Promise.all(tasks)
50}
51
52// ---------------------------------------------------------------------------
53
54export {
55 login,
56 serverLogin,
57 userLogin,
58 setAccessTokensToServers,
59 Server,
60 Client,
61 User
62}
diff --git a/shared/utils/users/user-subscriptions.ts b/shared/utils/users/user-subscriptions.ts
new file mode 100644
index 000000000..7148fbfca
--- /dev/null
+++ b/shared/utils/users/user-subscriptions.ts
@@ -0,0 +1,82 @@
1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../requests/requests'
2
3function addUserSubscription (url: string, token: string, targetUri: string, statusCodeExpected = 204) {
4 const path = '/api/v1/users/me/subscriptions'
5
6 return makePostBodyRequest({
7 url,
8 path,
9 token,
10 statusCodeExpected,
11 fields: { uri: targetUri }
12 })
13}
14
15function listUserSubscriptions (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) {
16 const path = '/api/v1/users/me/subscriptions'
17
18 return makeGetRequest({
19 url,
20 path,
21 token,
22 statusCodeExpected,
23 query: { sort }
24 })
25}
26
27function listUserSubscriptionVideos (url: string, token: string, sort = '-createdAt', statusCodeExpected = 200) {
28 const path = '/api/v1/users/me/subscriptions/videos'
29
30 return makeGetRequest({
31 url,
32 path,
33 token,
34 statusCodeExpected,
35 query: { sort }
36 })
37}
38
39function getUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 200) {
40 const path = '/api/v1/users/me/subscriptions/' + uri
41
42 return makeGetRequest({
43 url,
44 path,
45 token,
46 statusCodeExpected
47 })
48}
49
50function removeUserSubscription (url: string, token: string, uri: string, statusCodeExpected = 204) {
51 const path = '/api/v1/users/me/subscriptions/' + uri
52
53 return makeDeleteRequest({
54 url,
55 path,
56 token,
57 statusCodeExpected
58 })
59}
60
61function areSubscriptionsExist (url: string, token: string, uris: string[], statusCodeExpected = 200) {
62 const path = '/api/v1/users/me/subscriptions/exist'
63
64 return makeGetRequest({
65 url,
66 path,
67 query: { 'uris[]': uris },
68 token,
69 statusCodeExpected
70 })
71}
72
73// ---------------------------------------------------------------------------
74
75export {
76 areSubscriptionsExist,
77 addUserSubscription,
78 listUserSubscriptions,
79 getUserSubscription,
80 listUserSubscriptionVideos,
81 removeUserSubscription
82}
diff --git a/shared/utils/users/users.ts b/shared/utils/users/users.ts
new file mode 100644
index 000000000..554e42c01
--- /dev/null
+++ b/shared/utils/users/users.ts
@@ -0,0 +1,298 @@
1import * as request from 'supertest'
2import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
3
4import { UserRole } from '../../index'
5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
6
7function createUser (
8 url: string,
9 accessToken: string,
10 username: string,
11 password: string,
12 videoQuota = 1000000,
13 videoQuotaDaily = -1,
14 role: UserRole = UserRole.USER,
15 specialStatus = 200
16) {
17 const path = '/api/v1/users'
18 const body = {
19 username,
20 password,
21 role,
22 email: username + '@example.com',
23 videoQuota,
24 videoQuotaDaily
25 }
26
27 return request(url)
28 .post(path)
29 .set('Accept', 'application/json')
30 .set('Authorization', 'Bearer ' + accessToken)
31 .send(body)
32 .expect(specialStatus)
33}
34
35function registerUser (url: string, username: string, password: string, specialStatus = 204) {
36 const path = '/api/v1/users/register'
37 const body = {
38 username,
39 password,
40 email: username + '@example.com'
41 }
42
43 return request(url)
44 .post(path)
45 .set('Accept', 'application/json')
46 .send(body)
47 .expect(specialStatus)
48}
49
50function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
51 const path = '/api/v1/users/me'
52
53 return request(url)
54 .get(path)
55 .set('Accept', 'application/json')
56 .set('Authorization', 'Bearer ' + accessToken)
57 .expect(specialStatus)
58 .expect('Content-Type', /json/)
59}
60
61function deleteMe (url: string, accessToken: string, specialStatus = 204) {
62 const path = '/api/v1/users/me'
63
64 return request(url)
65 .delete(path)
66 .set('Accept', 'application/json')
67 .set('Authorization', 'Bearer ' + accessToken)
68 .expect(specialStatus)
69}
70
71function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = 200) {
72 const path = '/api/v1/users/me/video-quota-used'
73
74 return request(url)
75 .get(path)
76 .set('Accept', 'application/json')
77 .set('Authorization', 'Bearer ' + accessToken)
78 .expect(specialStatus)
79 .expect('Content-Type', /json/)
80}
81
82function getUserInformation (url: string, accessToken: string, userId: number) {
83 const path = '/api/v1/users/' + userId
84
85 return request(url)
86 .get(path)
87 .set('Accept', 'application/json')
88 .set('Authorization', 'Bearer ' + accessToken)
89 .expect(200)
90 .expect('Content-Type', /json/)
91}
92
93function getMyUserVideoRating (url: string, accessToken: string, videoId: number | string, specialStatus = 200) {
94 const path = '/api/v1/users/me/videos/' + videoId + '/rating'
95
96 return request(url)
97 .get(path)
98 .set('Accept', 'application/json')
99 .set('Authorization', 'Bearer ' + accessToken)
100 .expect(specialStatus)
101 .expect('Content-Type', /json/)
102}
103
104function getUsersList (url: string, accessToken: string) {
105 const path = '/api/v1/users'
106
107 return request(url)
108 .get(path)
109 .set('Accept', 'application/json')
110 .set('Authorization', 'Bearer ' + accessToken)
111 .expect(200)
112 .expect('Content-Type', /json/)
113}
114
115function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) {
116 const path = '/api/v1/users'
117
118 return request(url)
119 .get(path)
120 .query({ start })
121 .query({ count })
122 .query({ sort })
123 .query({ search })
124 .set('Accept', 'application/json')
125 .set('Authorization', 'Bearer ' + accessToken)
126 .expect(200)
127 .expect('Content-Type', /json/)
128}
129
130function removeUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) {
131 const path = '/api/v1/users'
132
133 return request(url)
134 .delete(path + '/' + userId)
135 .set('Accept', 'application/json')
136 .set('Authorization', 'Bearer ' + accessToken)
137 .expect(expectedStatus)
138}
139
140function blockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204, reason?: string) {
141 const path = '/api/v1/users'
142 let body: any
143 if (reason) body = { reason }
144
145 return request(url)
146 .post(path + '/' + userId + '/block')
147 .send(body)
148 .set('Accept', 'application/json')
149 .set('Authorization', 'Bearer ' + accessToken)
150 .expect(expectedStatus)
151}
152
153function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) {
154 const path = '/api/v1/users'
155
156 return request(url)
157 .post(path + '/' + userId + '/unblock')
158 .set('Accept', 'application/json')
159 .set('Authorization', 'Bearer ' + accessToken)
160 .expect(expectedStatus)
161}
162
163function updateMyUser (options: {
164 url: string
165 accessToken: string,
166 currentPassword?: string,
167 newPassword?: string,
168 nsfwPolicy?: NSFWPolicyType,
169 email?: string,
170 autoPlayVideo?: boolean
171 displayName?: string,
172 description?: string
173}) {
174 const path = '/api/v1/users/me'
175
176 const toSend = {}
177 if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword
178 if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword
179 if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy
180 if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
181 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
182 if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
183 if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName
184
185 return makePutBodyRequest({
186 url: options.url,
187 path,
188 token: options.accessToken,
189 fields: toSend,
190 statusCodeExpected: 204
191 })
192}
193
194function updateMyAvatar (options: {
195 url: string,
196 accessToken: string,
197 fixture: string
198}) {
199 const path = '/api/v1/users/me/avatar/pick'
200
201 return updateAvatarRequest(Object.assign(options, { path }))
202}
203
204function updateUser (options: {
205 url: string
206 userId: number,
207 accessToken: string,
208 email?: string,
209 emailVerified?: boolean,
210 videoQuota?: number,
211 videoQuotaDaily?: number,
212 role?: UserRole
213}) {
214 const path = '/api/v1/users/' + options.userId
215
216 const toSend = {}
217 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
218 if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified
219 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
220 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
221 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
222
223 return makePutBodyRequest({
224 url: options.url,
225 path,
226 token: options.accessToken,
227 fields: toSend,
228 statusCodeExpected: 204
229 })
230}
231
232function askResetPassword (url: string, email: string) {
233 const path = '/api/v1/users/ask-reset-password'
234
235 return makePostBodyRequest({
236 url,
237 path,
238 fields: { email },
239 statusCodeExpected: 204
240 })
241}
242
243function resetPassword (url: string, userId: number, verificationString: string, password: string, statusCodeExpected = 204) {
244 const path = '/api/v1/users/' + userId + '/reset-password'
245
246 return makePostBodyRequest({
247 url,
248 path,
249 fields: { password, verificationString },
250 statusCodeExpected
251 })
252}
253
254function askSendVerifyEmail (url: string, email: string) {
255 const path = '/api/v1/users/ask-send-verify-email'
256
257 return makePostBodyRequest({
258 url,
259 path,
260 fields: { email },
261 statusCodeExpected: 204
262 })
263}
264
265function verifyEmail (url: string, userId: number, verificationString: string, statusCodeExpected = 204) {
266 const path = '/api/v1/users/' + userId + '/verify-email'
267
268 return makePostBodyRequest({
269 url,
270 path,
271 fields: { verificationString },
272 statusCodeExpected
273 })
274}
275
276// ---------------------------------------------------------------------------
277
278export {
279 createUser,
280 registerUser,
281 getMyUserInformation,
282 getMyUserVideoRating,
283 deleteMe,
284 getMyUserVideoQuotaUsed,
285 getUsersList,
286 getUsersListPaginationAndSort,
287 removeUser,
288 updateUser,
289 updateMyUser,
290 getUserInformation,
291 blockUser,
292 unblockUser,
293 askResetPassword,
294 resetPassword,
295 updateMyAvatar,
296 askSendVerifyEmail,
297 verifyEmail
298}
diff --git a/shared/utils/videos/services.ts b/shared/utils/videos/services.ts
new file mode 100644
index 000000000..1a53dd4cf
--- /dev/null
+++ b/shared/utils/videos/services.ts
@@ -0,0 +1,23 @@
1import * as request from 'supertest'
2
3function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) {
4 const path = '/services/oembed'
5 const query = {
6 url: oembedUrl,
7 format,
8 maxheight: maxHeight,
9 maxwidth: maxWidth
10 }
11
12 return request(url)
13 .get(path)
14 .query(query)
15 .set('Accept', 'application/json')
16 .expect(200)
17}
18
19// ---------------------------------------------------------------------------
20
21export {
22 getOEmbed
23}
diff --git a/shared/utils/videos/video-abuses.ts b/shared/utils/videos/video-abuses.ts
new file mode 100644
index 000000000..7f011ec0f
--- /dev/null
+++ b/shared/utils/videos/video-abuses.ts
@@ -0,0 +1,65 @@
1import * as request from 'supertest'
2import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model'
3import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests'
4
5function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
6 const path = '/api/v1/videos/' + videoId + '/abuse'
7
8 return request(url)
9 .post(path)
10 .set('Accept', 'application/json')
11 .set('Authorization', 'Bearer ' + token)
12 .send({ reason })
13 .expect(specialStatus)
14}
15
16function getVideoAbusesList (url: string, token: string) {
17 const path = '/api/v1/videos/abuse'
18
19 return request(url)
20 .get(path)
21 .query({ sort: 'createdAt' })
22 .set('Accept', 'application/json')
23 .set('Authorization', 'Bearer ' + token)
24 .expect(200)
25 .expect('Content-Type', /json/)
26}
27
28function updateVideoAbuse (
29 url: string,
30 token: string,
31 videoId: string | number,
32 videoAbuseId: number,
33 body: VideoAbuseUpdate,
34 statusCodeExpected = 204
35) {
36 const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
37
38 return makePutBodyRequest({
39 url,
40 token,
41 path,
42 fields: body,
43 statusCodeExpected
44 })
45}
46
47function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) {
48 const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
49
50 return makeDeleteRequest({
51 url,
52 token,
53 path,
54 statusCodeExpected
55 })
56}
57
58// ---------------------------------------------------------------------------
59
60export {
61 reportVideoAbuse,
62 getVideoAbusesList,
63 updateVideoAbuse,
64 deleteVideoAbuse
65}
diff --git a/shared/utils/videos/video-blacklist.ts b/shared/utils/videos/video-blacklist.ts
new file mode 100644
index 000000000..2c176fde0
--- /dev/null
+++ b/shared/utils/videos/video-blacklist.ts
@@ -0,0 +1,67 @@
1import * as request from 'supertest'
2
3function addVideoToBlacklist (url: string, token: string, videoId: number | string, reason?: string, specialStatus = 204) {
4 const path = '/api/v1/videos/' + videoId + '/blacklist'
5
6 return request(url)
7 .post(path)
8 .send({ reason })
9 .set('Accept', 'application/json')
10 .set('Authorization', 'Bearer ' + token)
11 .expect(specialStatus)
12}
13
14function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
15 const path = '/api/v1/videos/' + videoId + '/blacklist'
16
17 return request(url)
18 .put(path)
19 .send({ reason })
20 .set('Accept', 'application/json')
21 .set('Authorization', 'Bearer ' + token)
22 .expect(specialStatus)
23}
24
25function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
26 const path = '/api/v1/videos/' + videoId + '/blacklist'
27
28 return request(url)
29 .delete(path)
30 .set('Accept', 'application/json')
31 .set('Authorization', 'Bearer ' + token)
32 .expect(specialStatus)
33}
34
35function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) {
36 const path = '/api/v1/videos/blacklist/'
37
38 return request(url)
39 .get(path)
40 .query({ sort: 'createdAt' })
41 .set('Accept', 'application/json')
42 .set('Authorization', 'Bearer ' + token)
43 .expect(specialStatus)
44 .expect('Content-Type', /json/)
45}
46
47function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
48 const path = '/api/v1/videos/blacklist/'
49
50 return request(url)
51 .get(path)
52 .query({ sort: sort })
53 .set('Accept', 'application/json')
54 .set('Authorization', 'Bearer ' + token)
55 .expect(specialStatus)
56 .expect('Content-Type', /json/)
57}
58
59// ---------------------------------------------------------------------------
60
61export {
62 addVideoToBlacklist,
63 removeVideoFromBlacklist,
64 getBlacklistedVideosList,
65 getSortedBlacklistedVideosList,
66 updateVideoBlacklist
67}
diff --git a/shared/utils/videos/video-captions.ts b/shared/utils/videos/video-captions.ts
new file mode 100644
index 000000000..8d67f617b
--- /dev/null
+++ b/shared/utils/videos/video-captions.ts
@@ -0,0 +1,71 @@
1import { makeDeleteRequest, makeGetRequest, makeUploadRequest } from '../requests/requests'
2import * as request from 'supertest'
3import * as chai from 'chai'
4import { buildAbsoluteFixturePath } from '../miscs/miscs'
5
6const expect = chai.expect
7
8function createVideoCaption (args: {
9 url: string,
10 accessToken: string
11 videoId: string | number
12 language: string
13 fixture: string,
14 mimeType?: string,
15 statusCodeExpected?: number
16}) {
17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language
18
19 const captionfile = buildAbsoluteFixturePath(args.fixture)
20 const captionfileAttach = args.mimeType ? [ captionfile, { contentType: args.mimeType } ] : captionfile
21
22 return makeUploadRequest({
23 method: 'PUT',
24 url: args.url,
25 path,
26 token: args.accessToken,
27 fields: {},
28 attaches: {
29 captionfile: captionfileAttach
30 },
31 statusCodeExpected: args.statusCodeExpected || 204
32 })
33}
34
35function listVideoCaptions (url: string, videoId: string | number) {
36 const path = '/api/v1/videos/' + videoId + '/captions'
37
38 return makeGetRequest({
39 url,
40 path,
41 statusCodeExpected: 200
42 })
43}
44
45function deleteVideoCaption (url: string, token: string, videoId: string | number, language: string) {
46 const path = '/api/v1/videos/' + videoId + '/captions/' + language
47
48 return makeDeleteRequest({
49 url,
50 token,
51 path,
52 statusCodeExpected: 204
53 })
54}
55
56async function testCaptionFile (url: string, captionPath: string, containsString: string) {
57 const res = await request(url)
58 .get(captionPath)
59 .expect(200)
60
61 expect(res.text).to.contain(containsString)
62}
63
64// ---------------------------------------------------------------------------
65
66export {
67 createVideoCaption,
68 listVideoCaptions,
69 testCaptionFile,
70 deleteVideoCaption
71}
diff --git a/shared/utils/videos/video-change-ownership.ts b/shared/utils/videos/video-change-ownership.ts
new file mode 100644
index 000000000..f288692ea
--- /dev/null
+++ b/shared/utils/videos/video-change-ownership.ts
@@ -0,0 +1,54 @@
1import * as request from 'supertest'
2
3function changeVideoOwnership (url: string, token: string, videoId: number | string, username) {
4 const path = '/api/v1/videos/' + videoId + '/give-ownership'
5
6 return request(url)
7 .post(path)
8 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + token)
10 .send({ username })
11 .expect(204)
12}
13
14function getVideoChangeOwnershipList (url: string, token: string) {
15 const path = '/api/v1/videos/ownership'
16
17 return request(url)
18 .get(path)
19 .query({ sort: '-createdAt' })
20 .set('Accept', 'application/json')
21 .set('Authorization', 'Bearer ' + token)
22 .expect(200)
23 .expect('Content-Type', /json/)
24}
25
26function acceptChangeOwnership (url: string, token: string, ownershipId: string, channelId: number, expectedStatus = 204) {
27 const path = '/api/v1/videos/ownership/' + ownershipId + '/accept'
28
29 return request(url)
30 .post(path)
31 .set('Accept', 'application/json')
32 .set('Authorization', 'Bearer ' + token)
33 .send({ channelId })
34 .expect(expectedStatus)
35}
36
37function refuseChangeOwnership (url: string, token: string, ownershipId: string, expectedStatus = 204) {
38 const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse'
39
40 return request(url)
41 .post(path)
42 .set('Accept', 'application/json')
43 .set('Authorization', 'Bearer ' + token)
44 .expect(expectedStatus)
45}
46
47// ---------------------------------------------------------------------------
48
49export {
50 changeVideoOwnership,
51 getVideoChangeOwnershipList,
52 acceptChangeOwnership,
53 refuseChangeOwnership
54}
diff --git a/shared/utils/videos/video-channels.ts b/shared/utils/videos/video-channels.ts
new file mode 100644
index 000000000..3935c261e
--- /dev/null
+++ b/shared/utils/videos/video-channels.ts
@@ -0,0 +1,118 @@
1import * as request from 'supertest'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
3import { updateAvatarRequest } from '../requests/requests'
4
5function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
6 const path = '/api/v1/video-channels'
7
8 const req = request(url)
9 .get(path)
10 .query({ start: start })
11 .query({ count: count })
12
13 if (sort) req.query({ sort })
14
15 return req.set('Accept', 'application/json')
16 .expect(200)
17 .expect('Content-Type', /json/)
18}
19
20function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) {
21 const path = '/api/v1/accounts/' + accountName + '/video-channels'
22
23 return request(url)
24 .get(path)
25 .set('Accept', 'application/json')
26 .expect(specialStatus)
27 .expect('Content-Type', /json/)
28}
29
30function addVideoChannel (
31 url: string,
32 token: string,
33 videoChannelAttributesArg: VideoChannelCreate,
34 expectedStatus = 200
35) {
36 const path = '/api/v1/video-channels/'
37
38 // Default attributes
39 let attributes = {
40 displayName: 'my super video channel',
41 description: 'my super channel description',
42 support: 'my super channel support'
43 }
44 attributes = Object.assign(attributes, videoChannelAttributesArg)
45
46 return request(url)
47 .post(path)
48 .send(attributes)
49 .set('Accept', 'application/json')
50 .set('Authorization', 'Bearer ' + token)
51 .expect(expectedStatus)
52}
53
54function updateVideoChannel (
55 url: string,
56 token: string,
57 channelName: string,
58 attributes: VideoChannelUpdate,
59 expectedStatus = 204
60) {
61 const body = {}
62 const path = '/api/v1/video-channels/' + channelName
63
64 if (attributes.displayName) body['displayName'] = attributes.displayName
65 if (attributes.description) body['description'] = attributes.description
66 if (attributes.support) body['support'] = attributes.support
67
68 return request(url)
69 .put(path)
70 .send(body)
71 .set('Accept', 'application/json')
72 .set('Authorization', 'Bearer ' + token)
73 .expect(expectedStatus)
74}
75
76function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = 204) {
77 const path = '/api/v1/video-channels/' + channelName
78
79 return request(url)
80 .delete(path)
81 .set('Accept', 'application/json')
82 .set('Authorization', 'Bearer ' + token)
83 .expect(expectedStatus)
84}
85
86function getVideoChannel (url: string, channelName: string) {
87 const path = '/api/v1/video-channels/' + channelName
88
89 return request(url)
90 .get(path)
91 .set('Accept', 'application/json')
92 .expect(200)
93 .expect('Content-Type', /json/)
94}
95
96function updateVideoChannelAvatar (options: {
97 url: string,
98 accessToken: string,
99 fixture: string,
100 videoChannelName: string | number
101}) {
102
103 const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick'
104
105 return updateAvatarRequest(Object.assign(options, { path }))
106}
107
108// ---------------------------------------------------------------------------
109
110export {
111 updateVideoChannelAvatar,
112 getVideoChannelsList,
113 getAccountVideoChannelsList,
114 addVideoChannel,
115 updateVideoChannel,
116 deleteVideoChannel,
117 getVideoChannel
118}
diff --git a/shared/utils/videos/video-comments.ts b/shared/utils/videos/video-comments.ts
new file mode 100644
index 000000000..0ebf69ced
--- /dev/null
+++ b/shared/utils/videos/video-comments.ts
@@ -0,0 +1,87 @@
1import * as request from 'supertest'
2import { makeDeleteRequest } from '../requests/requests'
3
4function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) {
5 const path = '/api/v1/videos/' + videoId + '/comment-threads'
6
7 const req = request(url)
8 .get(path)
9 .query({ start: start })
10 .query({ count: count })
11
12 if (sort) req.query({ sort })
13 if (token) req.set('Authorization', 'Bearer ' + token)
14
15 return req.set('Accept', 'application/json')
16 .expect(200)
17 .expect('Content-Type', /json/)
18}
19
20function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) {
21 const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId
22
23 const req = request(url)
24 .get(path)
25 .set('Accept', 'application/json')
26
27 if (token) req.set('Authorization', 'Bearer ' + token)
28
29 return req.expect(200)
30 .expect('Content-Type', /json/)
31}
32
33function addVideoCommentThread (url: string, token: string, videoId: number | string, text: string, expectedStatus = 200) {
34 const path = '/api/v1/videos/' + videoId + '/comment-threads'
35
36 return request(url)
37 .post(path)
38 .send({ text })
39 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + token)
41 .expect(expectedStatus)
42}
43
44function addVideoCommentReply (
45 url: string,
46 token: string,
47 videoId: number | string,
48 inReplyToCommentId: number,
49 text: string,
50 expectedStatus = 200
51) {
52 const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId
53
54 return request(url)
55 .post(path)
56 .send({ text })
57 .set('Accept', 'application/json')
58 .set('Authorization', 'Bearer ' + token)
59 .expect(expectedStatus)
60}
61
62function deleteVideoComment (
63 url: string,
64 token: string,
65 videoId: number | string,
66 commentId: number,
67 statusCodeExpected = 204
68) {
69 const path = '/api/v1/videos/' + videoId + '/comments/' + commentId
70
71 return makeDeleteRequest({
72 url,
73 path,
74 token,
75 statusCodeExpected
76 })
77}
78
79// ---------------------------------------------------------------------------
80
81export {
82 getVideoCommentThreads,
83 getVideoThreadComments,
84 addVideoCommentThread,
85 addVideoCommentReply,
86 deleteVideoComment
87}
diff --git a/shared/utils/videos/video-history.ts b/shared/utils/videos/video-history.ts
new file mode 100644
index 000000000..7635478f7
--- /dev/null
+++ b/shared/utils/videos/video-history.ts
@@ -0,0 +1,14 @@
1import { makePutBodyRequest } from '../requests/requests'
2
3function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number) {
4 const path = '/api/v1/videos/' + videoId + '/watching'
5 const fields = { currentTime }
6
7 return makePutBodyRequest({ url, path, token, fields, statusCodeExpected: 204 })
8}
9
10// ---------------------------------------------------------------------------
11
12export {
13 userWatchVideo
14}
diff --git a/shared/utils/videos/video-imports.ts b/shared/utils/videos/video-imports.ts
new file mode 100644
index 000000000..3fa49b432
--- /dev/null
+++ b/shared/utils/videos/video-imports.ts
@@ -0,0 +1,52 @@
1
2import { VideoImportCreate } from '../../models/videos'
3import { makeGetRequest, makeUploadRequest } from '../requests/requests'
4
5function getYoutubeVideoUrl () {
6 return 'https://youtu.be/msX3jv1XdvM'
7}
8
9function getMagnetURI () {
10 // tslint:disable:max-line-length
11 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'
12}
13
14function importVideo (url: string, token: string, attributes: VideoImportCreate) {
15 const path = '/api/v1/videos/imports'
16
17 let attaches: any = {}
18 if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile }
19
20 return makeUploadRequest({
21 url,
22 path,
23 token,
24 attaches,
25 fields: attributes,
26 statusCodeExpected: 200
27 })
28}
29
30function getMyVideoImports (url: string, token: string, sort?: string) {
31 const path = '/api/v1/users/me/videos/imports'
32
33 const query = {}
34 if (sort) query['sort'] = sort
35
36 return makeGetRequest({
37 url,
38 query,
39 path,
40 token,
41 statusCodeExpected: 200
42 })
43}
44
45// ---------------------------------------------------------------------------
46
47export {
48 getYoutubeVideoUrl,
49 importVideo,
50 getMagnetURI,
51 getMyVideoImports
52}
diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts
new file mode 100644
index 000000000..f5fcc6a8a
--- /dev/null
+++ b/shared/utils/videos/videos.ts
@@ -0,0 +1,577 @@
1/* tslint:disable:no-unused-expression */
2
3import { expect } from 'chai'
4import { existsSync, readdir, readFile } from 'fs-extra'
5import * as parseTorrent from 'parse-torrent'
6import { extname, join } from 'path'
7import * as request from 'supertest'
8import {
9 buildAbsoluteFixturePath,
10 getMyUserInformation,
11 immutableAssign,
12 makeGetRequest,
13 makePutBodyRequest,
14 makeUploadRequest,
15 root,
16 ServerInfo,
17 testImage
18} from '../'
19
20import { VideoDetails, VideoPrivacy } from '../../models/videos'
21import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
22import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
23
24type VideoAttributes = {
25 name?: string
26 category?: number
27 licence?: number
28 language?: string
29 nsfw?: boolean
30 commentsEnabled?: boolean
31 waitTranscoding?: boolean
32 description?: string
33 tags?: string[]
34 channelId?: number
35 privacy?: VideoPrivacy
36 fixture?: string
37 thumbnailfile?: string
38 previewfile?: string
39 scheduleUpdate?: {
40 updateAt: string
41 privacy?: VideoPrivacy
42 }
43}
44
45function getVideoCategories (url: string) {
46 const path = '/api/v1/videos/categories'
47
48 return makeGetRequest({
49 url,
50 path,
51 statusCodeExpected: 200
52 })
53}
54
55function getVideoLicences (url: string) {
56 const path = '/api/v1/videos/licences'
57
58 return makeGetRequest({
59 url,
60 path,
61 statusCodeExpected: 200
62 })
63}
64
65function getVideoLanguages (url: string) {
66 const path = '/api/v1/videos/languages'
67
68 return makeGetRequest({
69 url,
70 path,
71 statusCodeExpected: 200
72 })
73}
74
75function getVideoPrivacies (url: string) {
76 const path = '/api/v1/videos/privacies'
77
78 return makeGetRequest({
79 url,
80 path,
81 statusCodeExpected: 200
82 })
83}
84
85function getVideo (url: string, id: number | string, expectedStatus = 200) {
86 const path = '/api/v1/videos/' + id
87
88 return request(url)
89 .get(path)
90 .set('Accept', 'application/json')
91 .expect(expectedStatus)
92}
93
94function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
95 const path = '/api/v1/videos/' + id + '/views'
96
97 const req = request(url)
98 .post(path)
99 .set('Accept', 'application/json')
100
101 if (xForwardedFor) {
102 req.set('X-Forwarded-For', xForwardedFor)
103 }
104
105 return req.expect(expectedStatus)
106}
107
108function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
109 const path = '/api/v1/videos/' + id
110
111 return request(url)
112 .get(path)
113 .set('Authorization', 'Bearer ' + token)
114 .set('Accept', 'application/json')
115 .expect(expectedStatus)
116}
117
118function getVideoDescription (url: string, descriptionPath: string) {
119 return request(url)
120 .get(descriptionPath)
121 .set('Accept', 'application/json')
122 .expect(200)
123 .expect('Content-Type', /json/)
124}
125
126function getVideosList (url: string) {
127 const path = '/api/v1/videos'
128
129 return request(url)
130 .get(path)
131 .query({ sort: 'name' })
132 .set('Accept', 'application/json')
133 .expect(200)
134 .expect('Content-Type', /json/)
135}
136
137function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
138 const path = '/api/v1/videos'
139
140 return request(url)
141 .get(path)
142 .set('Authorization', 'Bearer ' + token)
143 .query(immutableAssign(query, { sort: 'name' }))
144 .set('Accept', 'application/json')
145 .expect(200)
146 .expect('Content-Type', /json/)
147}
148
149function getLocalVideos (url: string) {
150 const path = '/api/v1/videos'
151
152 return request(url)
153 .get(path)
154 .query({ sort: 'name', filter: 'local' })
155 .set('Accept', 'application/json')
156 .expect(200)
157 .expect('Content-Type', /json/)
158}
159
160function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
161 const path = '/api/v1/users/me/videos'
162
163 const req = request(url)
164 .get(path)
165 .query({ start: start })
166 .query({ count: count })
167
168 if (sort) req.query({ sort })
169
170 return req.set('Accept', 'application/json')
171 .set('Authorization', 'Bearer ' + accessToken)
172 .expect(200)
173 .expect('Content-Type', /json/)
174}
175
176function getAccountVideos (
177 url: string,
178 accessToken: string,
179 accountName: string,
180 start: number,
181 count: number,
182 sort?: string,
183 query: { nsfw?: boolean } = {}
184) {
185 const path = '/api/v1/accounts/' + accountName + '/videos'
186
187 return makeGetRequest({
188 url,
189 path,
190 query: immutableAssign(query, {
191 start,
192 count,
193 sort
194 }),
195 token: accessToken,
196 statusCodeExpected: 200
197 })
198}
199
200function getVideoChannelVideos (
201 url: string,
202 accessToken: string,
203 videoChannelName: string,
204 start: number,
205 count: number,
206 sort?: string,
207 query: { nsfw?: boolean } = {}
208) {
209 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
210
211 return makeGetRequest({
212 url,
213 path,
214 query: immutableAssign(query, {
215 start,
216 count,
217 sort
218 }),
219 token: accessToken,
220 statusCodeExpected: 200
221 })
222}
223
224function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
225 const path = '/api/v1/videos'
226
227 const req = request(url)
228 .get(path)
229 .query({ start: start })
230 .query({ count: count })
231
232 if (sort) req.query({ sort })
233
234 return req.set('Accept', 'application/json')
235 .expect(200)
236 .expect('Content-Type', /json/)
237}
238
239function getVideosListSort (url: string, sort: string) {
240 const path = '/api/v1/videos'
241
242 return request(url)
243 .get(path)
244 .query({ sort: sort })
245 .set('Accept', 'application/json')
246 .expect(200)
247 .expect('Content-Type', /json/)
248}
249
250function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
251 const path = '/api/v1/videos'
252
253 return request(url)
254 .get(path)
255 .query(query)
256 .set('Accept', 'application/json')
257 .expect(200)
258 .expect('Content-Type', /json/)
259}
260
261function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
262 const path = '/api/v1/videos'
263
264 return request(url)
265 .delete(path + '/' + id)
266 .set('Accept', 'application/json')
267 .set('Authorization', 'Bearer ' + token)
268 .expect(expectedStatus)
269}
270
271async function checkVideoFilesWereRemoved (
272 videoUUID: string,
273 serverNumber: number,
274 directories = [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]
275) {
276 const testDirectory = 'test' + serverNumber
277
278 for (const directory of directories) {
279 const directoryPath = join(root(), testDirectory, directory)
280
281 const directoryExists = existsSync(directoryPath)
282 expect(directoryExists).to.be.true
283
284 const files = await readdir(directoryPath)
285 for (const file of files) {
286 expect(file).to.not.contain(videoUUID)
287 }
288 }
289}
290
291async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
292 const path = '/api/v1/videos/upload'
293 let defaultChannelId = '1'
294
295 try {
296 const res = await getMyUserInformation(url, accessToken)
297 defaultChannelId = res.body.videoChannels[0].id
298 } catch (e) { /* empty */ }
299
300 // Override default attributes
301 const attributes = Object.assign({
302 name: 'my super video',
303 category: 5,
304 licence: 4,
305 language: 'zh',
306 channelId: defaultChannelId,
307 nsfw: true,
308 waitTranscoding: false,
309 description: 'my super description',
310 support: 'my super support text',
311 tags: [ 'tag' ],
312 privacy: VideoPrivacy.PUBLIC,
313 commentsEnabled: true,
314 fixture: 'video_short.webm'
315 }, videoAttributesArg)
316
317 const req = request(url)
318 .post(path)
319 .set('Accept', 'application/json')
320 .set('Authorization', 'Bearer ' + accessToken)
321 .field('name', attributes.name)
322 .field('nsfw', JSON.stringify(attributes.nsfw))
323 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
324 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
325 .field('privacy', attributes.privacy.toString())
326 .field('channelId', attributes.channelId)
327
328 if (attributes.description !== undefined) {
329 req.field('description', attributes.description)
330 }
331 if (attributes.language !== undefined) {
332 req.field('language', attributes.language.toString())
333 }
334 if (attributes.category !== undefined) {
335 req.field('category', attributes.category.toString())
336 }
337 if (attributes.licence !== undefined) {
338 req.field('licence', attributes.licence.toString())
339 }
340
341 for (let i = 0; i < attributes.tags.length; i++) {
342 req.field('tags[' + i + ']', attributes.tags[i])
343 }
344
345 if (attributes.thumbnailfile !== undefined) {
346 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
347 }
348 if (attributes.previewfile !== undefined) {
349 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
350 }
351
352 if (attributes.scheduleUpdate) {
353 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
354
355 if (attributes.scheduleUpdate.privacy) {
356 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
357 }
358 }
359
360 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
361 .expect(specialStatus)
362}
363
364function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
365 const path = '/api/v1/videos/' + id
366 const body = {}
367
368 if (attributes.name) body['name'] = attributes.name
369 if (attributes.category) body['category'] = attributes.category
370 if (attributes.licence) body['licence'] = attributes.licence
371 if (attributes.language) body['language'] = attributes.language
372 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
373 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
374 if (attributes.description) body['description'] = attributes.description
375 if (attributes.tags) body['tags'] = attributes.tags
376 if (attributes.privacy) body['privacy'] = attributes.privacy
377 if (attributes.channelId) body['channelId'] = attributes.channelId
378 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
379
380 // Upload request
381 if (attributes.thumbnailfile || attributes.previewfile) {
382 const attaches: any = {}
383 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
384 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
385
386 return makeUploadRequest({
387 url,
388 method: 'PUT',
389 path,
390 token: accessToken,
391 fields: body,
392 attaches,
393 statusCodeExpected
394 })
395 }
396
397 return makePutBodyRequest({
398 url,
399 path,
400 fields: body,
401 token: accessToken,
402 statusCodeExpected
403 })
404}
405
406function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
407 const path = '/api/v1/videos/' + id + '/rate'
408
409 return request(url)
410 .put(path)
411 .set('Accept', 'application/json')
412 .set('Authorization', 'Bearer ' + accessToken)
413 .send({ rating })
414 .expect(specialStatus)
415}
416
417function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
418 return new Promise<any>((res, rej) => {
419 const torrentName = videoUUID + '-' + resolution + '.torrent'
420 const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName)
421 readFile(torrentPath, (err, data) => {
422 if (err) return rej(err)
423
424 return res(parseTorrent(data))
425 })
426 })
427}
428
429async function completeVideoCheck (
430 url: string,
431 video: any,
432 attributes: {
433 name: string
434 category: number
435 licence: number
436 language: string
437 nsfw: boolean
438 commentsEnabled: boolean
439 description: string
440 publishedAt?: string
441 support: string
442 account: {
443 name: string
444 host: string
445 }
446 isLocal: boolean
447 tags: string[]
448 privacy: number
449 likes?: number
450 dislikes?: number
451 duration: number
452 channel: {
453 displayName: string
454 name: string
455 description
456 isLocal: boolean
457 }
458 fixture: string
459 files: {
460 resolution: number
461 size: number
462 }[],
463 thumbnailfile?: string
464 previewfile?: string
465 }
466) {
467 if (!attributes.likes) attributes.likes = 0
468 if (!attributes.dislikes) attributes.dislikes = 0
469
470 expect(video.name).to.equal(attributes.name)
471 expect(video.category.id).to.equal(attributes.category)
472 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
473 expect(video.licence.id).to.equal(attributes.licence)
474 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
475 expect(video.language.id).to.equal(attributes.language)
476 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
477 expect(video.privacy.id).to.deep.equal(attributes.privacy)
478 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
479 expect(video.nsfw).to.equal(attributes.nsfw)
480 expect(video.description).to.equal(attributes.description)
481 expect(video.account.id).to.be.a('number')
482 expect(video.account.uuid).to.be.a('string')
483 expect(video.account.host).to.equal(attributes.account.host)
484 expect(video.account.name).to.equal(attributes.account.name)
485 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
486 expect(video.channel.name).to.equal(attributes.channel.name)
487 expect(video.likes).to.equal(attributes.likes)
488 expect(video.dislikes).to.equal(attributes.dislikes)
489 expect(video.isLocal).to.equal(attributes.isLocal)
490 expect(video.duration).to.equal(attributes.duration)
491 expect(dateIsValid(video.createdAt)).to.be.true
492 expect(dateIsValid(video.publishedAt)).to.be.true
493 expect(dateIsValid(video.updatedAt)).to.be.true
494
495 if (attributes.publishedAt) {
496 expect(video.publishedAt).to.equal(attributes.publishedAt)
497 }
498
499 const res = await getVideo(url, video.uuid)
500 const videoDetails: VideoDetails = res.body
501
502 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
503 expect(videoDetails.tags).to.deep.equal(attributes.tags)
504 expect(videoDetails.account.name).to.equal(attributes.account.name)
505 expect(videoDetails.account.host).to.equal(attributes.account.host)
506 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
507 expect(video.channel.name).to.equal(attributes.channel.name)
508 expect(videoDetails.channel.host).to.equal(attributes.account.host)
509 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
510 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
511 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
512 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
513
514 for (const attributeFile of attributes.files) {
515 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
516 expect(file).not.to.be.undefined
517
518 let extension = extname(attributes.fixture)
519 // Transcoding enabled on server 2, extension will always be .mp4
520 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
521
522 const magnetUri = file.magnetUri
523 expect(file.magnetUri).to.have.lengthOf.above(2)
524 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
525 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
526 expect(file.resolution.id).to.equal(attributeFile.resolution)
527 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
528
529 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
530 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
531 expect(file.size,
532 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
533 .to.be.above(minSize).and.below(maxSize)
534
535 {
536 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
537 }
538
539 if (attributes.previewfile) {
540 await testImage(url, attributes.previewfile, videoDetails.previewPath)
541 }
542
543 const torrent = await webtorrentAdd(magnetUri, true)
544 expect(torrent.files).to.be.an('array')
545 expect(torrent.files.length).to.equal(1)
546 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
547 }
548}
549
550// ---------------------------------------------------------------------------
551
552export {
553 getVideoDescription,
554 getVideoCategories,
555 getVideoLicences,
556 getVideoPrivacies,
557 getVideoLanguages,
558 getMyVideos,
559 getAccountVideos,
560 getVideoChannelVideos,
561 getVideo,
562 getVideoWithToken,
563 getVideosList,
564 getVideosListPagination,
565 getVideosListSort,
566 removeVideo,
567 getVideosListWithToken,
568 uploadVideo,
569 getVideosWithFilters,
570 updateVideo,
571 rateVideo,
572 viewVideo,
573 parseTorrentVideo,
574 getLocalVideos,
575 completeVideoCheck,
576 checkVideoFilesWereRemoved
577}