aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/utils/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-02-11 11:52:34 +0100
committerChocobozzz <me@florianbigard.com>2019-02-11 11:52:34 +0100
commit88108880bbdba473cfe36ecbebc1c3c4f972e102 (patch)
treeb242efb3b4f0d7e49d88f2d1f2063b5b3b0489c0 /shared/utils/server
parent53a94c7cfa8368da4cd248d65df8346905938f0c (diff)
parent9b712a2017e4ab3cf12cd6bd58278905520159d0 (diff)
downloadPeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.gz
PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.zst
PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.zip
Merge branch 'develop' into pr/1217
Diffstat (limited to 'shared/utils/server')
-rw-r--r--shared/utils/server/activitypub.ts14
-rw-r--r--shared/utils/server/clients.ts19
-rw-r--r--shared/utils/server/config.ts142
-rw-r--r--shared/utils/server/contact-form.ts28
-rw-r--r--shared/utils/server/follows.ts79
-rw-r--r--shared/utils/server/jobs.ts82
-rw-r--r--shared/utils/server/redundancy.ts17
-rw-r--r--shared/utils/server/servers.ts215
-rw-r--r--shared/utils/server/stats.ts22
9 files changed, 618 insertions, 0 deletions
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..29c24cff9
--- /dev/null
+++ b/shared/utils/server/config.ts
@@ -0,0 +1,142 @@
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 contactForm: {
84 enabled: true
85 },
86 user: {
87 videoQuota: 5242881,
88 videoQuotaDaily: 318742
89 },
90 transcoding: {
91 enabled: true,
92 allowAdditionalExtensions: true,
93 threads: 1,
94 resolutions: {
95 '240p': false,
96 '360p': true,
97 '480p': true,
98 '720p': false,
99 '1080p': false
100 },
101 hls: {
102 enabled: false
103 }
104 },
105 import: {
106 videos: {
107 http: {
108 enabled: false
109 },
110 torrent: {
111 enabled: false
112 }
113 }
114 }
115 }
116
117 Object.assign(updateParams, newConfig)
118
119 return updateCustomConfig(url, token, updateParams)
120}
121
122function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) {
123 const path = '/api/v1/config/custom'
124
125 return makeDeleteRequest({
126 url,
127 token,
128 path,
129 statusCodeExpected
130 })
131}
132
133// ---------------------------------------------------------------------------
134
135export {
136 getConfig,
137 getCustomConfig,
138 updateCustomConfig,
139 getAbout,
140 deleteCustomConfig,
141 updateCustomSubConfig
142}
diff --git a/shared/utils/server/contact-form.ts b/shared/utils/server/contact-form.ts
new file mode 100644
index 000000000..80394cf99
--- /dev/null
+++ b/shared/utils/server/contact-form.ts
@@ -0,0 +1,28 @@
1import * as request from 'supertest'
2import { ContactForm } from '../../models/server'
3
4function sendContactForm (options: {
5 url: string,
6 fromEmail: string,
7 fromName: string,
8 body: string,
9 expectedStatus?: number
10}) {
11 const path = '/api/v1/server/contact'
12
13 const body: ContactForm = {
14 fromEmail: options.fromEmail,
15 fromName: options.fromName,
16 body: options.body
17 }
18 return request(options.url)
19 .post(path)
20 .send(body)
21 .expect(options.expectedStatus || 204)
22}
23
24// ---------------------------------------------------------------------------
25
26export {
27 sendContactForm
28}
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..692b5e24d
--- /dev/null
+++ b/shared/utils/server/jobs.ts
@@ -0,0 +1,82 @@
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 const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) : 2000
33 let servers: ServerInfo[]
34
35 if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ]
36 else servers = serversArg as ServerInfo[]
37
38 const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
39 let pendingRequests = false
40
41 function tasksBuilder () {
42 const tasks: Promise<any>[] = []
43 pendingRequests = false
44
45 // Check if each server has pending request
46 for (const server of servers) {
47 for (const state of states) {
48 const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt')
49 .then(res => res.body.data)
50 .then((jobs: Job[]) => jobs.filter(j => j.type !== 'videos-views'))
51 .then(jobs => {
52 if (jobs.length !== 0) pendingRequests = true
53 })
54 tasks.push(p)
55 }
56 }
57
58 return tasks
59 }
60
61 do {
62 await Promise.all(tasksBuilder())
63
64 // Retry, in case of new jobs were created
65 if (pendingRequests === false) {
66 await wait(pendingJobWait)
67 await Promise.all(tasksBuilder())
68 }
69
70 if (pendingRequests) {
71 await wait(1000)
72 }
73 } while (pendingRequests)
74}
75
76// ---------------------------------------------------------------------------
77
78export {
79 getJobsList,
80 waitJobs,
81 getJobsListPaginationAndSort
82}
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..bde7dd5c2
--- /dev/null
+++ b/shared/utils/server/servers.ts
@@ -0,0 +1,215 @@
1/* tslint:disable:no-unused-expression */
2
3import { ChildProcess, exec, fork } from 'child_process'
4import { join } from 'path'
5import { root, wait } from '../miscs/miscs'
6import { readdir, readFile } from 'fs-extra'
7import { existsSync } from 'fs'
8import { expect } from 'chai'
9
10interface ServerInfo {
11 app: ChildProcess,
12 url: string
13 host: string
14 serverNumber: number
15
16 client: {
17 id: string,
18 secret: string
19 }
20
21 user: {
22 username: string,
23 password: string,
24 email?: string
25 }
26
27 accessToken?: string
28
29 video?: {
30 id: number
31 uuid: string
32 name: string
33 account: {
34 name: string
35 }
36 }
37
38 remoteVideo?: {
39 id: number
40 uuid: string
41 }
42}
43
44function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
45 let apps = []
46 let i = 0
47
48 return new Promise<ServerInfo[]>(res => {
49 function anotherServerDone (serverNumber, app) {
50 apps[serverNumber - 1] = app
51 i++
52 if (i === totalServers) {
53 return res(apps)
54 }
55 }
56
57 flushTests()
58 .then(() => {
59 for (let j = 1; j <= totalServers; j++) {
60 runServer(j, configOverride).then(app => anotherServerDone(j, app))
61 }
62 })
63 })
64}
65
66function flushTests () {
67 return new Promise<void>((res, rej) => {
68 return exec('npm run clean:server:test', err => {
69 if (err) return rej(err)
70
71 return res()
72 })
73 })
74}
75
76function runServer (serverNumber: number, configOverride?: Object, args = []) {
77 const server: ServerInfo = {
78 app: null,
79 serverNumber: serverNumber,
80 url: `http://localhost:${9000 + serverNumber}`,
81 host: `localhost:${9000 + serverNumber}`,
82 client: {
83 id: null,
84 secret: null
85 },
86 user: {
87 username: null,
88 password: null
89 }
90 }
91
92 // These actions are async so we need to be sure that they have both been done
93 const serverRunString = {
94 'Server listening': false
95 }
96 const key = 'Database peertube_test' + serverNumber + ' is ready'
97 serverRunString[key] = false
98
99 const regexps = {
100 client_id: 'Client id: (.+)',
101 client_secret: 'Client secret: (.+)',
102 user_username: 'Username: (.+)',
103 user_password: 'User password: (.+)'
104 }
105
106 // Share the environment
107 const env = Object.create(process.env)
108 env['NODE_ENV'] = 'test'
109 env['NODE_APP_INSTANCE'] = serverNumber.toString()
110
111 if (configOverride !== undefined) {
112 env['NODE_CONFIG'] = JSON.stringify(configOverride)
113 }
114
115 const options = {
116 silent: true,
117 env: env,
118 detached: true
119 }
120
121 return new Promise<ServerInfo>(res => {
122 server.app = fork(join(root(), 'dist', 'server.js'), args, options)
123 server.app.stdout.on('data', function onStdout (data) {
124 let dontContinue = false
125
126 // Capture things if we want to
127 for (const key of Object.keys(regexps)) {
128 const regexp = regexps[key]
129 const matches = data.toString().match(regexp)
130 if (matches !== null) {
131 if (key === 'client_id') server.client.id = matches[1]
132 else if (key === 'client_secret') server.client.secret = matches[1]
133 else if (key === 'user_username') server.user.username = matches[1]
134 else if (key === 'user_password') server.user.password = matches[1]
135 }
136 }
137
138 // Check if all required sentences are here
139 for (const key of Object.keys(serverRunString)) {
140 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
141 if (serverRunString[key] === false) dontContinue = true
142 }
143
144 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
145 if (dontContinue === true) return
146
147 server.app.stdout.removeListener('data', onStdout)
148
149 process.on('exit', () => {
150 try {
151 process.kill(server.app.pid)
152 } catch { /* empty */ }
153 })
154
155 res(server)
156 })
157
158 })
159}
160
161async function reRunServer (server: ServerInfo, configOverride?: any) {
162 const newServer = await runServer(server.serverNumber, configOverride)
163 server.app = newServer.app
164
165 return server
166}
167
168async function checkTmpIsEmpty (server: ServerInfo) {
169 return checkDirectoryIsEmpty(server, 'tmp')
170}
171
172async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
173 const testDirectory = 'test' + server.serverNumber
174
175 const directoryPath = join(root(), testDirectory, directory)
176
177 const directoryExists = existsSync(directoryPath)
178 expect(directoryExists).to.be.true
179
180 const files = await readdir(directoryPath)
181 expect(files).to.have.lengthOf(0)
182}
183
184function killallServers (servers: ServerInfo[]) {
185 for (const server of servers) {
186 process.kill(-server.app.pid)
187 }
188}
189
190async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
191 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log')
192
193 while (true) {
194 const buf = await readFile(logfile)
195
196 const matches = buf.toString().match(new RegExp(str, 'g'))
197 if (matches && matches.length === count) return
198
199 await wait(1000)
200 }
201}
202
203// ---------------------------------------------------------------------------
204
205export {
206 checkDirectoryIsEmpty,
207 checkTmpIsEmpty,
208 ServerInfo,
209 flushAndRunMultipleServers,
210 flushTests,
211 runServer,
212 killallServers,
213 reRunServer,
214 waitUntilLog
215}
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}