aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/utils
diff options
context:
space:
mode:
Diffstat (limited to 'shared/utils')
-rw-r--r--shared/utils/cli/cli.ts24
-rw-r--r--shared/utils/feeds/feeds.ts32
-rw-r--r--shared/utils/index.ts18
-rw-r--r--shared/utils/miscs/email.ts25
-rw-r--r--shared/utils/miscs/miscs.ts101
-rw-r--r--shared/utils/overviews/overviews.ts18
-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.ts15
-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.ts77
-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.ts198
-rw-r--r--shared/utils/users/login.ts62
-rw-r--r--shared/utils/users/user-subscriptions.ts82
-rw-r--r--shared/utils/users/users.ts296
-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.ts51
-rw-r--r--shared/utils/videos/videos.ts576
33 files changed, 2901 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..897389824
--- /dev/null
+++ b/shared/utils/index.ts
@@ -0,0 +1,18 @@
1export * from './server/activitypub'
2export * from './cli/cli'
3export * from './server/clients'
4export * from './server/config'
5export * from './users/login'
6export * from './miscs/miscs'
7export * from './server/follows'
8export * from './requests/requests'
9export * from './server/servers'
10export * from './videos/services'
11export * from './users/users'
12export * from './videos/video-abuses'
13export * from './videos/video-blacklist'
14export * from './videos/video-channels'
15export * from './videos/videos'
16export * from './videos/video-change-ownership'
17export * from './feeds/feeds'
18export * 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..589daa420
--- /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 server/tests/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(__dirname, '..', '..', '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(__dirname, '..', '..', '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/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/check-api-params.ts b/shared/utils/requests/check-api-params.ts
new file mode 100644
index 000000000..edb47e0e9
--- /dev/null
+++ b/shared/utils/requests/check-api-params.ts
@@ -0,0 +1,40 @@
1import { makeGetRequest } from './requests'
2import { immutableAssign } from '..'
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..5796540f7
--- /dev/null
+++ b/shared/utils/requests/requests.ts
@@ -0,0 +1,168 @@
1import * as request from 'supertest'
2import { buildAbsoluteFixturePath } 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(__dirname, '..', '..', '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..115b3ff9a
--- /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 '..'
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..cf3c1c3b3
--- /dev/null
+++ b/shared/utils/server/activitypub.ts
@@ -0,0 +1,15 @@
1import * as request from 'supertest'
2
3function makeActivityPubGetRequest (url: string, path: string) {
4 return request(url)
5 .get(path)
6 .set('Accept', 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8')
7 .expect(200)
8 .expect('Content-Type', /json/)
9}
10
11// ---------------------------------------------------------------------------
12
13export {
14 makeActivityPubGetRequest
15}
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..15a94432b
--- /dev/null
+++ b/shared/utils/server/config.ts
@@ -0,0 +1,135 @@
1import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../'
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..3eec1d7d7
--- /dev/null
+++ b/shared/utils/server/jobs.ts
@@ -0,0 +1,77 @@
1import * as request from 'supertest'
2import { Job, JobState } from '../../models'
3import { ServerInfo, wait } from '../index'
4
5function getJobsList (url: string, accessToken: string, state: JobState) {
6 const path = '/api/v1/jobs/' + state
7
8 return request(url)
9 .get(path)
10 .set('Accept', 'application/json')
11 .set('Authorization', 'Bearer ' + accessToken)
12 .expect(200)
13 .expect('Content-Type', /json/)
14}
15
16function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) {
17 const path = '/api/v1/jobs/' + state
18
19 return request(url)
20 .get(path)
21 .query({ start })
22 .query({ count })
23 .query({ sort })
24 .set('Accept', 'application/json')
25 .set('Authorization', 'Bearer ' + accessToken)
26 .expect(200)
27 .expect('Content-Type', /json/)
28}
29
30async function waitJobs (serversArg: ServerInfo[] | ServerInfo) {
31 let servers: ServerInfo[]
32
33 if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ]
34 else servers = serversArg as ServerInfo[]
35
36 const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
37 const tasks: Promise<any>[] = []
38 let pendingRequests: boolean
39
40 do {
41 pendingRequests = false
42
43 // Check if each server has pending request
44 for (const server of servers) {
45 for (const state of states) {
46 const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt')
47 .then(res => res.body.data)
48 .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views'))
49 .then(jobs => {
50 if (jobs.length !== 0) pendingRequests = true
51 })
52 tasks.push(p)
53 }
54 }
55
56 await Promise.all(tasks)
57
58 // Retry, in case of new jobs were created
59 if (pendingRequests === false) {
60 await wait(1000)
61
62 await Promise.all(tasks)
63 }
64
65 if (pendingRequests) {
66 await wait(1000)
67 }
68 } while (pendingRequests)
69}
70
71// ---------------------------------------------------------------------------
72
73export {
74 getJobsList,
75 waitJobs,
76 getJobsListPaginationAndSort
77}
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..f358a21f1
--- /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(__dirname, '..', '..', '..', '..', '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..01989d952
--- /dev/null
+++ b/shared/utils/server/stats.ts
@@ -0,0 +1,22 @@
1import { makeGetRequest } from '../'
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..5601f2a77
--- /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 '../index'
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..35b537571
--- /dev/null
+++ b/shared/utils/users/blocklist.ts
@@ -0,0 +1,198 @@
1/* tslint:disable:no-unused-expression */
2
3import { makeDeleteRequest, makePostBodyRequest } from '../index'
4import { makeGetRequest } from '../requests/requests'
5
6function getAccountBlocklistByAccount (
7 url: string,
8 token: string,
9 start: number,
10 count: number,
11 sort = '-createdAt',
12 statusCodeExpected = 200
13) {
14 const path = '/api/v1/users/me/blocklist/accounts'
15
16 return makeGetRequest({
17 url,
18 token,
19 query: { start, count, sort },
20 path,
21 statusCodeExpected
22 })
23}
24
25function addAccountToAccountBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) {
26 const path = '/api/v1/users/me/blocklist/accounts'
27
28 return makePostBodyRequest({
29 url,
30 path,
31 token,
32 fields: {
33 accountName: accountToBlock
34 },
35 statusCodeExpected
36 })
37}
38
39function removeAccountFromAccountBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) {
40 const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock
41
42 return makeDeleteRequest({
43 url,
44 path,
45 token,
46 statusCodeExpected
47 })
48}
49
50function getServerBlocklistByAccount (
51 url: string,
52 token: string,
53 start: number,
54 count: number,
55 sort = '-createdAt',
56 statusCodeExpected = 200
57) {
58 const path = '/api/v1/users/me/blocklist/servers'
59
60 return makeGetRequest({
61 url,
62 token,
63 query: { start, count, sort },
64 path,
65 statusCodeExpected
66 })
67}
68
69function addServerToAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
70 const path = '/api/v1/users/me/blocklist/servers'
71
72 return makePostBodyRequest({
73 url,
74 path,
75 token,
76 fields: {
77 host: serverToBlock
78 },
79 statusCodeExpected
80 })
81}
82
83function removeServerFromAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
84 const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock
85
86 return makeDeleteRequest({
87 url,
88 path,
89 token,
90 statusCodeExpected
91 })
92}
93
94function getAccountBlocklistByServer (
95 url: string,
96 token: string,
97 start: number,
98 count: number,
99 sort = '-createdAt',
100 statusCodeExpected = 200
101) {
102 const path = '/api/v1/server/blocklist/accounts'
103
104 return makeGetRequest({
105 url,
106 token,
107 query: { start, count, sort },
108 path,
109 statusCodeExpected
110 })
111}
112
113function addAccountToServerBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) {
114 const path = '/api/v1/server/blocklist/accounts'
115
116 return makePostBodyRequest({
117 url,
118 path,
119 token,
120 fields: {
121 accountName: accountToBlock
122 },
123 statusCodeExpected
124 })
125}
126
127function removeAccountFromServerBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) {
128 const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock
129
130 return makeDeleteRequest({
131 url,
132 path,
133 token,
134 statusCodeExpected
135 })
136}
137
138function getServerBlocklistByServer (
139 url: string,
140 token: string,
141 start: number,
142 count: number,
143 sort = '-createdAt',
144 statusCodeExpected = 200
145) {
146 const path = '/api/v1/server/blocklist/servers'
147
148 return makeGetRequest({
149 url,
150 token,
151 query: { start, count, sort },
152 path,
153 statusCodeExpected
154 })
155}
156
157function addServerToServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
158 const path = '/api/v1/server/blocklist/servers'
159
160 return makePostBodyRequest({
161 url,
162 path,
163 token,
164 fields: {
165 host: serverToBlock
166 },
167 statusCodeExpected
168 })
169}
170
171function removeServerFromServerBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
172 const path = '/api/v1/server/blocklist/servers/' + serverToBlock
173
174 return makeDeleteRequest({
175 url,
176 path,
177 token,
178 statusCodeExpected
179 })
180}
181
182// ---------------------------------------------------------------------------
183
184export {
185 getAccountBlocklistByAccount,
186 addAccountToAccountBlocklist,
187 removeAccountFromAccountBlocklist,
188 getServerBlocklistByAccount,
189 addServerToAccountBlocklist,
190 removeServerFromAccountBlocklist,
191
192 getAccountBlocklistByServer,
193 addAccountToServerBlocklist,
194 removeAccountFromServerBlocklist,
195 getServerBlocklistByServer,
196 addServerToServerBlocklist,
197 removeServerFromServerBlocklist
198}
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..b0e7da7cc
--- /dev/null
+++ b/shared/utils/users/user-subscriptions.ts
@@ -0,0 +1,82 @@
1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../'
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..1b385aaf7
--- /dev/null
+++ b/shared/utils/users/users.ts
@@ -0,0 +1,296 @@
1import * as request from 'supertest'
2import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../'
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 videoQuota?: number,
210 videoQuotaDaily?: number,
211 role?: UserRole
212}) {
213 const path = '/api/v1/users/' + options.userId
214
215 const toSend = {}
216 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
217 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
218 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
219 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
220
221 return makePutBodyRequest({
222 url: options.url,
223 path,
224 token: options.accessToken,
225 fields: toSend,
226 statusCodeExpected: 204
227 })
228}
229
230function askResetPassword (url: string, email: string) {
231 const path = '/api/v1/users/ask-reset-password'
232
233 return makePostBodyRequest({
234 url,
235 path,
236 fields: { email },
237 statusCodeExpected: 204
238 })
239}
240
241function resetPassword (url: string, userId: number, verificationString: string, password: string, statusCodeExpected = 204) {
242 const path = '/api/v1/users/' + userId + '/reset-password'
243
244 return makePostBodyRequest({
245 url,
246 path,
247 fields: { password, verificationString },
248 statusCodeExpected
249 })
250}
251
252function askSendVerifyEmail (url: string, email: string) {
253 const path = '/api/v1/users/ask-send-verify-email'
254
255 return makePostBodyRequest({
256 url,
257 path,
258 fields: { email },
259 statusCodeExpected: 204
260 })
261}
262
263function verifyEmail (url: string, userId: number, verificationString: string, statusCodeExpected = 204) {
264 const path = '/api/v1/users/' + userId + '/verify-email'
265
266 return makePostBodyRequest({
267 url,
268 path,
269 fields: { verificationString },
270 statusCodeExpected
271 })
272}
273
274// ---------------------------------------------------------------------------
275
276export {
277 createUser,
278 registerUser,
279 getMyUserInformation,
280 getMyUserVideoRating,
281 deleteMe,
282 getMyUserVideoQuotaUsed,
283 getUsersList,
284 getUsersListPaginationAndSort,
285 removeUser,
286 updateUser,
287 updateMyUser,
288 getUserInformation,
289 blockUser,
290 unblockUser,
291 askResetPassword,
292 resetPassword,
293 updateMyAvatar,
294 askSendVerifyEmail,
295 verifyEmail
296}
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..8be891a9b
--- /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 '..'
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..41e52be07
--- /dev/null
+++ b/shared/utils/videos/video-captions.ts
@@ -0,0 +1,71 @@
1import { makeDeleteRequest, makeGetRequest } from '../'
2import { buildAbsoluteFixturePath, makeUploadRequest } from '../index'
3import * as request from 'supertest'
4import * as chai from 'chai'
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..3d37f0e4c
--- /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 '../index'
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..7d4cae364
--- /dev/null
+++ b/shared/utils/videos/video-comments.ts
@@ -0,0 +1,87 @@
1import * as request from 'supertest'
2import { makeDeleteRequest } from '../'
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..bf5062cd1
--- /dev/null
+++ b/shared/utils/videos/video-imports.ts
@@ -0,0 +1,51 @@
1import { VideoImportCreate } from '../../models/videos'
2import { makeGetRequest, makeUploadRequest } from '..'
3
4function getYoutubeVideoUrl () {
5 return 'https://youtu.be/msX3jv1XdvM'
6}
7
8function getMagnetURI () {
9 // tslint:disable:max-line-length
10 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'
11}
12
13function importVideo (url: string, token: string, attributes: VideoImportCreate) {
14 const path = '/api/v1/videos/imports'
15
16 let attaches: any = {}
17 if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile }
18
19 return makeUploadRequest({
20 url,
21 path,
22 token,
23 attaches,
24 fields: attributes,
25 statusCodeExpected: 200
26 })
27}
28
29function getMyVideoImports (url: string, token: string, sort?: string) {
30 const path = '/api/v1/users/me/videos/imports'
31
32 const query = {}
33 if (sort) query['sort'] = sort
34
35 return makeGetRequest({
36 url,
37 query,
38 path,
39 token,
40 statusCodeExpected: 200
41 })
42}
43
44// ---------------------------------------------------------------------------
45
46export {
47 getYoutubeVideoUrl,
48 importVideo,
49 getMagnetURI,
50 getMyVideoImports
51}
diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts
new file mode 100644
index 000000000..b3206e566
--- /dev/null
+++ b/shared/utils/videos/videos.ts
@@ -0,0 +1,576 @@
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 '../'
19import { VideoDetails, VideoPrivacy } from '../../models/videos'
20import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers'
21import { dateIsValid, webtorrentAdd } from '../index'
22
23type VideoAttributes = {
24 name?: string
25 category?: number
26 licence?: number
27 language?: string
28 nsfw?: boolean
29 commentsEnabled?: boolean
30 waitTranscoding?: boolean
31 description?: string
32 tags?: string[]
33 channelId?: number
34 privacy?: VideoPrivacy
35 fixture?: string
36 thumbnailfile?: string
37 previewfile?: string
38 scheduleUpdate?: {
39 updateAt: string
40 privacy?: VideoPrivacy
41 }
42}
43
44function getVideoCategories (url: string) {
45 const path = '/api/v1/videos/categories'
46
47 return makeGetRequest({
48 url,
49 path,
50 statusCodeExpected: 200
51 })
52}
53
54function getVideoLicences (url: string) {
55 const path = '/api/v1/videos/licences'
56
57 return makeGetRequest({
58 url,
59 path,
60 statusCodeExpected: 200
61 })
62}
63
64function getVideoLanguages (url: string) {
65 const path = '/api/v1/videos/languages'
66
67 return makeGetRequest({
68 url,
69 path,
70 statusCodeExpected: 200
71 })
72}
73
74function getVideoPrivacies (url: string) {
75 const path = '/api/v1/videos/privacies'
76
77 return makeGetRequest({
78 url,
79 path,
80 statusCodeExpected: 200
81 })
82}
83
84function getVideo (url: string, id: number | string, expectedStatus = 200) {
85 const path = '/api/v1/videos/' + id
86
87 return request(url)
88 .get(path)
89 .set('Accept', 'application/json')
90 .expect(expectedStatus)
91}
92
93function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
94 const path = '/api/v1/videos/' + id + '/views'
95
96 const req = request(url)
97 .post(path)
98 .set('Accept', 'application/json')
99
100 if (xForwardedFor) {
101 req.set('X-Forwarded-For', xForwardedFor)
102 }
103
104 return req.expect(expectedStatus)
105}
106
107function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
108 const path = '/api/v1/videos/' + id
109
110 return request(url)
111 .get(path)
112 .set('Authorization', 'Bearer ' + token)
113 .set('Accept', 'application/json')
114 .expect(expectedStatus)
115}
116
117function getVideoDescription (url: string, descriptionPath: string) {
118 return request(url)
119 .get(descriptionPath)
120 .set('Accept', 'application/json')
121 .expect(200)
122 .expect('Content-Type', /json/)
123}
124
125function getVideosList (url: string) {
126 const path = '/api/v1/videos'
127
128 return request(url)
129 .get(path)
130 .query({ sort: 'name' })
131 .set('Accept', 'application/json')
132 .expect(200)
133 .expect('Content-Type', /json/)
134}
135
136function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
137 const path = '/api/v1/videos'
138
139 return request(url)
140 .get(path)
141 .set('Authorization', 'Bearer ' + token)
142 .query(immutableAssign(query, { sort: 'name' }))
143 .set('Accept', 'application/json')
144 .expect(200)
145 .expect('Content-Type', /json/)
146}
147
148function getLocalVideos (url: string) {
149 const path = '/api/v1/videos'
150
151 return request(url)
152 .get(path)
153 .query({ sort: 'name', filter: 'local' })
154 .set('Accept', 'application/json')
155 .expect(200)
156 .expect('Content-Type', /json/)
157}
158
159function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
160 const path = '/api/v1/users/me/videos'
161
162 const req = request(url)
163 .get(path)
164 .query({ start: start })
165 .query({ count: count })
166
167 if (sort) req.query({ sort })
168
169 return req.set('Accept', 'application/json')
170 .set('Authorization', 'Bearer ' + accessToken)
171 .expect(200)
172 .expect('Content-Type', /json/)
173}
174
175function getAccountVideos (
176 url: string,
177 accessToken: string,
178 accountName: string,
179 start: number,
180 count: number,
181 sort?: string,
182 query: { nsfw?: boolean } = {}
183) {
184 const path = '/api/v1/accounts/' + accountName + '/videos'
185
186 return makeGetRequest({
187 url,
188 path,
189 query: immutableAssign(query, {
190 start,
191 count,
192 sort
193 }),
194 token: accessToken,
195 statusCodeExpected: 200
196 })
197}
198
199function getVideoChannelVideos (
200 url: string,
201 accessToken: string,
202 videoChannelName: string,
203 start: number,
204 count: number,
205 sort?: string,
206 query: { nsfw?: boolean } = {}
207) {
208 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
209
210 return makeGetRequest({
211 url,
212 path,
213 query: immutableAssign(query, {
214 start,
215 count,
216 sort
217 }),
218 token: accessToken,
219 statusCodeExpected: 200
220 })
221}
222
223function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
224 const path = '/api/v1/videos'
225
226 const req = request(url)
227 .get(path)
228 .query({ start: start })
229 .query({ count: count })
230
231 if (sort) req.query({ sort })
232
233 return req.set('Accept', 'application/json')
234 .expect(200)
235 .expect('Content-Type', /json/)
236}
237
238function getVideosListSort (url: string, sort: string) {
239 const path = '/api/v1/videos'
240
241 return request(url)
242 .get(path)
243 .query({ sort: sort })
244 .set('Accept', 'application/json')
245 .expect(200)
246 .expect('Content-Type', /json/)
247}
248
249function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
250 const path = '/api/v1/videos'
251
252 return request(url)
253 .get(path)
254 .query(query)
255 .set('Accept', 'application/json')
256 .expect(200)
257 .expect('Content-Type', /json/)
258}
259
260function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
261 const path = '/api/v1/videos'
262
263 return request(url)
264 .delete(path + '/' + id)
265 .set('Accept', 'application/json')
266 .set('Authorization', 'Bearer ' + token)
267 .expect(expectedStatus)
268}
269
270async function checkVideoFilesWereRemoved (
271 videoUUID: string,
272 serverNumber: number,
273 directories = [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]
274) {
275 const testDirectory = 'test' + serverNumber
276
277 for (const directory of directories) {
278 const directoryPath = join(root(), testDirectory, directory)
279
280 const directoryExists = existsSync(directoryPath)
281 expect(directoryExists).to.be.true
282
283 const files = await readdir(directoryPath)
284 for (const file of files) {
285 expect(file).to.not.contain(videoUUID)
286 }
287 }
288}
289
290async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
291 const path = '/api/v1/videos/upload'
292 let defaultChannelId = '1'
293
294 try {
295 const res = await getMyUserInformation(url, accessToken)
296 defaultChannelId = res.body.videoChannels[0].id
297 } catch (e) { /* empty */ }
298
299 // Override default attributes
300 const attributes = Object.assign({
301 name: 'my super video',
302 category: 5,
303 licence: 4,
304 language: 'zh',
305 channelId: defaultChannelId,
306 nsfw: true,
307 waitTranscoding: false,
308 description: 'my super description',
309 support: 'my super support text',
310 tags: [ 'tag' ],
311 privacy: VideoPrivacy.PUBLIC,
312 commentsEnabled: true,
313 fixture: 'video_short.webm'
314 }, videoAttributesArg)
315
316 const req = request(url)
317 .post(path)
318 .set('Accept', 'application/json')
319 .set('Authorization', 'Bearer ' + accessToken)
320 .field('name', attributes.name)
321 .field('nsfw', JSON.stringify(attributes.nsfw))
322 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
323 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
324 .field('privacy', attributes.privacy.toString())
325 .field('channelId', attributes.channelId)
326
327 if (attributes.description !== undefined) {
328 req.field('description', attributes.description)
329 }
330 if (attributes.language !== undefined) {
331 req.field('language', attributes.language.toString())
332 }
333 if (attributes.category !== undefined) {
334 req.field('category', attributes.category.toString())
335 }
336 if (attributes.licence !== undefined) {
337 req.field('licence', attributes.licence.toString())
338 }
339
340 for (let i = 0; i < attributes.tags.length; i++) {
341 req.field('tags[' + i + ']', attributes.tags[i])
342 }
343
344 if (attributes.thumbnailfile !== undefined) {
345 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
346 }
347 if (attributes.previewfile !== undefined) {
348 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
349 }
350
351 if (attributes.scheduleUpdate) {
352 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
353
354 if (attributes.scheduleUpdate.privacy) {
355 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
356 }
357 }
358
359 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
360 .expect(specialStatus)
361}
362
363function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
364 const path = '/api/v1/videos/' + id
365 const body = {}
366
367 if (attributes.name) body['name'] = attributes.name
368 if (attributes.category) body['category'] = attributes.category
369 if (attributes.licence) body['licence'] = attributes.licence
370 if (attributes.language) body['language'] = attributes.language
371 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
372 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
373 if (attributes.description) body['description'] = attributes.description
374 if (attributes.tags) body['tags'] = attributes.tags
375 if (attributes.privacy) body['privacy'] = attributes.privacy
376 if (attributes.channelId) body['channelId'] = attributes.channelId
377 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
378
379 // Upload request
380 if (attributes.thumbnailfile || attributes.previewfile) {
381 const attaches: any = {}
382 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
383 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
384
385 return makeUploadRequest({
386 url,
387 method: 'PUT',
388 path,
389 token: accessToken,
390 fields: body,
391 attaches,
392 statusCodeExpected
393 })
394 }
395
396 return makePutBodyRequest({
397 url,
398 path,
399 fields: body,
400 token: accessToken,
401 statusCodeExpected
402 })
403}
404
405function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
406 const path = '/api/v1/videos/' + id + '/rate'
407
408 return request(url)
409 .put(path)
410 .set('Accept', 'application/json')
411 .set('Authorization', 'Bearer ' + accessToken)
412 .send({ rating })
413 .expect(specialStatus)
414}
415
416function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
417 return new Promise<any>((res, rej) => {
418 const torrentName = videoUUID + '-' + resolution + '.torrent'
419 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
420 readFile(torrentPath, (err, data) => {
421 if (err) return rej(err)
422
423 return res(parseTorrent(data))
424 })
425 })
426}
427
428async function completeVideoCheck (
429 url: string,
430 video: any,
431 attributes: {
432 name: string
433 category: number
434 licence: number
435 language: string
436 nsfw: boolean
437 commentsEnabled: boolean
438 description: string
439 publishedAt?: string
440 support: string
441 account: {
442 name: string
443 host: string
444 }
445 isLocal: boolean
446 tags: string[]
447 privacy: number
448 likes?: number
449 dislikes?: number
450 duration: number
451 channel: {
452 displayName: string
453 name: string
454 description
455 isLocal: boolean
456 }
457 fixture: string
458 files: {
459 resolution: number
460 size: number
461 }[],
462 thumbnailfile?: string
463 previewfile?: string
464 }
465) {
466 if (!attributes.likes) attributes.likes = 0
467 if (!attributes.dislikes) attributes.dislikes = 0
468
469 expect(video.name).to.equal(attributes.name)
470 expect(video.category.id).to.equal(attributes.category)
471 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
472 expect(video.licence.id).to.equal(attributes.licence)
473 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
474 expect(video.language.id).to.equal(attributes.language)
475 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
476 expect(video.privacy.id).to.deep.equal(attributes.privacy)
477 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
478 expect(video.nsfw).to.equal(attributes.nsfw)
479 expect(video.description).to.equal(attributes.description)
480 expect(video.account.id).to.be.a('number')
481 expect(video.account.uuid).to.be.a('string')
482 expect(video.account.host).to.equal(attributes.account.host)
483 expect(video.account.name).to.equal(attributes.account.name)
484 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
485 expect(video.channel.name).to.equal(attributes.channel.name)
486 expect(video.likes).to.equal(attributes.likes)
487 expect(video.dislikes).to.equal(attributes.dislikes)
488 expect(video.isLocal).to.equal(attributes.isLocal)
489 expect(video.duration).to.equal(attributes.duration)
490 expect(dateIsValid(video.createdAt)).to.be.true
491 expect(dateIsValid(video.publishedAt)).to.be.true
492 expect(dateIsValid(video.updatedAt)).to.be.true
493
494 if (attributes.publishedAt) {
495 expect(video.publishedAt).to.equal(attributes.publishedAt)
496 }
497
498 const res = await getVideo(url, video.uuid)
499 const videoDetails: VideoDetails = res.body
500
501 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
502 expect(videoDetails.tags).to.deep.equal(attributes.tags)
503 expect(videoDetails.account.name).to.equal(attributes.account.name)
504 expect(videoDetails.account.host).to.equal(attributes.account.host)
505 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
506 expect(video.channel.name).to.equal(attributes.channel.name)
507 expect(videoDetails.channel.host).to.equal(attributes.account.host)
508 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
509 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
510 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
511 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
512
513 for (const attributeFile of attributes.files) {
514 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
515 expect(file).not.to.be.undefined
516
517 let extension = extname(attributes.fixture)
518 // Transcoding enabled on server 2, extension will always be .mp4
519 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
520
521 const magnetUri = file.magnetUri
522 expect(file.magnetUri).to.have.lengthOf.above(2)
523 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
524 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
525 expect(file.resolution.id).to.equal(attributeFile.resolution)
526 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
527
528 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
529 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
530 expect(file.size,
531 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
532 .to.be.above(minSize).and.below(maxSize)
533
534 {
535 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
536 }
537
538 if (attributes.previewfile) {
539 await testImage(url, attributes.previewfile, videoDetails.previewPath)
540 }
541
542 const torrent = await webtorrentAdd(magnetUri, true)
543 expect(torrent.files).to.be.an('array')
544 expect(torrent.files.length).to.equal(1)
545 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
546 }
547}
548
549// ---------------------------------------------------------------------------
550
551export {
552 getVideoDescription,
553 getVideoCategories,
554 getVideoLicences,
555 getVideoPrivacies,
556 getVideoLanguages,
557 getMyVideos,
558 getAccountVideos,
559 getVideoChannelVideos,
560 getVideo,
561 getVideoWithToken,
562 getVideosList,
563 getVideosListPagination,
564 getVideosListSort,
565 removeVideo,
566 getVideosListWithToken,
567 uploadVideo,
568 getVideosWithFilters,
569 updateVideo,
570 rateVideo,
571 viewVideo,
572 parseTorrentVideo,
573 getLocalVideos,
574 completeVideoCheck,
575 checkVideoFilesWereRemoved
576}