]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - shared/server-commands/requests/requests.ts
b247017fd31a7db915819e0f53fc6e97abe3201a
[github/Chocobozzz/PeerTube.git] / shared / server-commands / requests / requests.ts
1 /* eslint-disable @typescript-eslint/no-floating-promises */
2
3 import { decode } from 'querystring'
4 import request from 'supertest'
5 import { URL } from 'url'
6 import { buildAbsoluteFixturePath, pick } from '@shared/core-utils'
7 import { HttpStatusCode } from '@shared/models'
8
9 export type CommonRequestParams = {
10 url: string
11 path?: string
12 contentType?: string
13 range?: string
14 redirects?: number
15 accept?: string
16 host?: string
17 token?: string
18 headers?: { [ name: string ]: string }
19 type?: string
20 xForwardedFor?: string
21 expectedStatus?: HttpStatusCode
22 }
23
24 function makeRawRequest (options: {
25 url: string
26 token?: string
27 expectedStatus?: HttpStatusCode
28 range?: string
29 query?: { [ id: string ]: string }
30 }) {
31 const { host, protocol, pathname } = new URL(options.url)
32
33 return makeGetRequest({
34 url: `${protocol}//${host}`,
35 path: pathname,
36
37 ...pick(options, [ 'expectedStatus', 'range', 'token', 'query' ])
38 })
39 }
40
41 function makeGetRequest (options: CommonRequestParams & {
42 query?: any
43 rawQuery?: string
44 }) {
45 const req = request(options.url).get(options.path)
46
47 if (options.query) req.query(options.query)
48 if (options.rawQuery) req.query(options.rawQuery)
49
50 return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
51 }
52
53 function makeHTMLRequest (url: string, path: string) {
54 return makeGetRequest({
55 url,
56 path,
57 accept: 'text/html',
58 expectedStatus: HttpStatusCode.OK_200
59 })
60 }
61
62 function makeActivityPubGetRequest (url: string, path: string, expectedStatus = HttpStatusCode.OK_200) {
63 return makeGetRequest({
64 url,
65 path,
66 expectedStatus,
67 accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8'
68 })
69 }
70
71 function makeDeleteRequest (options: CommonRequestParams & {
72 query?: any
73 rawQuery?: string
74 }) {
75 const req = request(options.url).delete(options.path)
76
77 if (options.query) req.query(options.query)
78 if (options.rawQuery) req.query(options.rawQuery)
79
80 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
81 }
82
83 function makeUploadRequest (options: CommonRequestParams & {
84 method?: 'POST' | 'PUT'
85
86 fields: { [ fieldName: string ]: any }
87 attaches?: { [ attachName: string ]: any | any[] }
88 }) {
89 let req = options.method === 'PUT'
90 ? request(options.url).put(options.path)
91 : request(options.url).post(options.path)
92
93 req = buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
94
95 buildFields(req, options.fields)
96
97 Object.keys(options.attaches || {}).forEach(attach => {
98 const value = options.attaches[attach]
99 if (!value) return
100
101 if (Array.isArray(value)) {
102 req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1])
103 } else {
104 req.attach(attach, buildAbsoluteFixturePath(value))
105 }
106 })
107
108 return req
109 }
110
111 function makePostBodyRequest (options: CommonRequestParams & {
112 fields?: { [ fieldName: string ]: any }
113 }) {
114 const req = request(options.url).post(options.path)
115 .send(options.fields)
116
117 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
118 }
119
120 function makePutBodyRequest (options: {
121 url: string
122 path: string
123 token?: string
124 fields: { [ fieldName: string ]: any }
125 expectedStatus?: HttpStatusCode
126 }) {
127 const req = request(options.url).put(options.path)
128 .send(options.fields)
129
130 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
131 }
132
133 function decodeQueryString (path: string) {
134 return decode(path.split('?')[1])
135 }
136
137 function unwrapBody <T> (test: request.Test): Promise<T> {
138 return test.then(res => res.body)
139 }
140
141 function unwrapText (test: request.Test): Promise<string> {
142 return test.then(res => res.text)
143 }
144
145 function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
146 return test.then(res => {
147 if (res.body instanceof Buffer) {
148 try {
149 return JSON.parse(new TextDecoder().decode(res.body))
150 } catch (err) {
151 console.error('Cannot decode JSON.', res.body)
152 throw err
153 }
154 }
155
156 return res.body
157 })
158 }
159
160 function unwrapTextOrDecode (test: request.Test): Promise<string> {
161 return test.then(res => res.text || new TextDecoder().decode(res.body))
162 }
163
164 // ---------------------------------------------------------------------------
165
166 export {
167 makeHTMLRequest,
168 makeGetRequest,
169 decodeQueryString,
170 makeUploadRequest,
171 makePostBodyRequest,
172 makePutBodyRequest,
173 makeDeleteRequest,
174 makeRawRequest,
175 makeActivityPubGetRequest,
176 unwrapBody,
177 unwrapTextOrDecode,
178 unwrapBodyOrDecodeToJSON,
179 unwrapText
180 }
181
182 // ---------------------------------------------------------------------------
183
184 function buildRequest (req: request.Test, options: CommonRequestParams) {
185 if (options.contentType) req.set('Accept', options.contentType)
186 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
187 if (options.range) req.set('Range', options.range)
188 if (options.accept) req.set('Accept', options.accept)
189 if (options.host) req.set('Host', options.host)
190 if (options.redirects) req.redirects(options.redirects)
191 if (options.xForwardedFor) req.set('X-Forwarded-For', options.xForwardedFor)
192 if (options.type) req.type(options.type)
193
194 Object.keys(options.headers || {}).forEach(name => {
195 req.set(name, options.headers[name])
196 })
197
198 return req.expect((res) => {
199 if (options.expectedStatus && res.status !== options.expectedStatus) {
200 throw new Error(`Expected status ${options.expectedStatus}, got ${res.status}. ` +
201 `\nThe server responded this error: "${res.body?.error ?? res.text}".\n` +
202 'You may take a closer look at the logs. To see how to do so, check out this page: ' +
203 'https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/development/tests.md#debug-server-logs')
204 }
205 return res
206 })
207 }
208
209 function buildFields (req: request.Test, fields: { [ fieldName: string ]: any }, namespace?: string) {
210 if (!fields) return
211
212 let formKey: string
213
214 for (const key of Object.keys(fields)) {
215 if (namespace) formKey = `${namespace}[${key}]`
216 else formKey = key
217
218 if (fields[key] === undefined) continue
219
220 if (Array.isArray(fields[key]) && fields[key].length === 0) {
221 req.field(key, [])
222 continue
223 }
224
225 if (fields[key] !== null && typeof fields[key] === 'object') {
226 buildFields(req, fields[key], formKey)
227 } else {
228 req.field(formKey, fields[key])
229 }
230 }
231 }