aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages/server-commands/src/requests/requests.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /packages/server-commands/src/requests/requests.ts
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'packages/server-commands/src/requests/requests.ts')
-rw-r--r--packages/server-commands/src/requests/requests.ts260
1 files changed, 260 insertions, 0 deletions
diff --git a/packages/server-commands/src/requests/requests.ts b/packages/server-commands/src/requests/requests.ts
new file mode 100644
index 000000000..ac143ea5d
--- /dev/null
+++ b/packages/server-commands/src/requests/requests.ts
@@ -0,0 +1,260 @@
1/* eslint-disable @typescript-eslint/no-floating-promises */
2
3import { decode } from 'querystring'
4import request from 'supertest'
5import { URL } from 'url'
6import { pick } from '@peertube/peertube-core-utils'
7import { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models'
8import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
9
10export type CommonRequestParams = {
11 url: string
12 path?: string
13 contentType?: string
14 responseType?: string
15 range?: string
16 redirects?: number
17 accept?: string
18 host?: string
19 token?: string
20 headers?: { [ name: string ]: string }
21 type?: string
22 xForwardedFor?: string
23 expectedStatus?: HttpStatusCodeType
24}
25
26function makeRawRequest (options: {
27 url: string
28 token?: string
29 expectedStatus?: HttpStatusCodeType
30 range?: string
31 query?: { [ id: string ]: string }
32 method?: 'GET' | 'POST'
33 headers?: { [ name: string ]: string }
34}) {
35 const { host, protocol, pathname } = new URL(options.url)
36
37 const reqOptions = {
38 url: `${protocol}//${host}`,
39 path: pathname,
40 contentType: undefined,
41
42 ...pick(options, [ 'expectedStatus', 'range', 'token', 'query', 'headers' ])
43 }
44
45 if (options.method === 'POST') {
46 return makePostBodyRequest(reqOptions)
47 }
48
49 return makeGetRequest(reqOptions)
50}
51
52function makeGetRequest (options: CommonRequestParams & {
53 query?: any
54 rawQuery?: string
55}) {
56 const req = request(options.url).get(options.path)
57
58 if (options.query) req.query(options.query)
59 if (options.rawQuery) req.query(options.rawQuery)
60
61 return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
62}
63
64function makeHTMLRequest (url: string, path: string) {
65 return makeGetRequest({
66 url,
67 path,
68 accept: 'text/html',
69 expectedStatus: HttpStatusCode.OK_200
70 })
71}
72
73function makeActivityPubGetRequest (url: string, path: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.OK_200) {
74 return makeGetRequest({
75 url,
76 path,
77 expectedStatus,
78 accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8'
79 })
80}
81
82function makeDeleteRequest (options: CommonRequestParams & {
83 query?: any
84 rawQuery?: string
85}) {
86 const req = request(options.url).delete(options.path)
87
88 if (options.query) req.query(options.query)
89 if (options.rawQuery) req.query(options.rawQuery)
90
91 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
92}
93
94function makeUploadRequest (options: CommonRequestParams & {
95 method?: 'POST' | 'PUT'
96
97 fields: { [ fieldName: string ]: any }
98 attaches?: { [ attachName: string ]: any | any[] }
99}) {
100 let req = options.method === 'PUT'
101 ? request(options.url).put(options.path)
102 : request(options.url).post(options.path)
103
104 req = buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
105
106 buildFields(req, options.fields)
107
108 Object.keys(options.attaches || {}).forEach(attach => {
109 const value = options.attaches[attach]
110 if (!value) return
111
112 if (Array.isArray(value)) {
113 req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1])
114 } else {
115 req.attach(attach, buildAbsoluteFixturePath(value))
116 }
117 })
118
119 return req
120}
121
122function makePostBodyRequest (options: CommonRequestParams & {
123 fields?: { [ fieldName: string ]: any }
124}) {
125 const req = request(options.url).post(options.path)
126 .send(options.fields)
127
128 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
129}
130
131function makePutBodyRequest (options: {
132 url: string
133 path: string
134 token?: string
135 fields: { [ fieldName: string ]: any }
136 expectedStatus?: HttpStatusCodeType
137 headers?: { [name: string]: string }
138}) {
139 const req = request(options.url).put(options.path)
140 .send(options.fields)
141
142 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
143}
144
145function decodeQueryString (path: string) {
146 return decode(path.split('?')[1])
147}
148
149// ---------------------------------------------------------------------------
150
151function unwrapBody <T> (test: request.Test): Promise<T> {
152 return test.then(res => res.body)
153}
154
155function unwrapText (test: request.Test): Promise<string> {
156 return test.then(res => res.text)
157}
158
159function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
160 return test.then(res => {
161 if (res.body instanceof Buffer) {
162 try {
163 return JSON.parse(new TextDecoder().decode(res.body))
164 } catch (err) {
165 console.error('Cannot decode JSON.', { res, body: res.body instanceof Buffer ? res.body.toString() : res.body })
166 throw err
167 }
168 }
169
170 if (res.text) {
171 try {
172 return JSON.parse(res.text)
173 } catch (err) {
174 console.error('Cannot decode json', { res, text: res.text })
175 throw err
176 }
177 }
178
179 return res.body
180 })
181}
182
183function unwrapTextOrDecode (test: request.Test): Promise<string> {
184 return test.then(res => res.text || new TextDecoder().decode(res.body))
185}
186
187// ---------------------------------------------------------------------------
188
189export {
190 makeHTMLRequest,
191 makeGetRequest,
192 decodeQueryString,
193 makeUploadRequest,
194 makePostBodyRequest,
195 makePutBodyRequest,
196 makeDeleteRequest,
197 makeRawRequest,
198 makeActivityPubGetRequest,
199 unwrapBody,
200 unwrapTextOrDecode,
201 unwrapBodyOrDecodeToJSON,
202 unwrapText
203}
204
205// ---------------------------------------------------------------------------
206
207function buildRequest (req: request.Test, options: CommonRequestParams) {
208 if (options.contentType) req.set('Accept', options.contentType)
209 if (options.responseType) req.responseType(options.responseType)
210 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
211 if (options.range) req.set('Range', options.range)
212 if (options.accept) req.set('Accept', options.accept)
213 if (options.host) req.set('Host', options.host)
214 if (options.redirects) req.redirects(options.redirects)
215 if (options.xForwardedFor) req.set('X-Forwarded-For', options.xForwardedFor)
216 if (options.type) req.type(options.type)
217
218 Object.keys(options.headers || {}).forEach(name => {
219 req.set(name, options.headers[name])
220 })
221
222 return req.expect(res => {
223 if (options.expectedStatus && res.status !== options.expectedStatus) {
224 const err = new Error(`Expected status ${options.expectedStatus}, got ${res.status}. ` +
225 `\nThe server responded: "${res.body?.error ?? res.text}".\n` +
226 'You may take a closer look at the logs. To see how to do so, check out this page: ' +
227 'https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/development/tests.md#debug-server-logs');
228
229 (err as any).res = res
230
231 throw err
232 }
233
234 return res
235 })
236}
237
238function buildFields (req: request.Test, fields: { [ fieldName: string ]: any }, namespace?: string) {
239 if (!fields) return
240
241 let formKey: string
242
243 for (const key of Object.keys(fields)) {
244 if (namespace) formKey = `${namespace}[${key}]`
245 else formKey = key
246
247 if (fields[key] === undefined) continue
248
249 if (Array.isArray(fields[key]) && fields[key].length === 0) {
250 req.field(key, [])
251 continue
252 }
253
254 if (fields[key] !== null && typeof fields[key] === 'object') {
255 buildFields(req, fields[key], formKey)
256 } else {
257 req.field(formKey, fields[key])
258 }
259 }
260}