1 /* eslint-disable @typescript-eslint/no-floating-promises */
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'
9 export type CommonRequestParams = {
18 headers?: { [ name: string ]: string }
20 xForwardedFor?: string
21 expectedStatus?: HttpStatusCode
24 function makeRawRequest (options: {
27 expectedStatus?: HttpStatusCode
29 query?: { [ id: string ]: string }
31 const { host, protocol, pathname } = new URL(options.url)
33 return makeGetRequest({
34 url: `${protocol}//${host}`,
37 ...pick(options, [ 'expectedStatus', 'range', 'token', 'query' ])
41 function makeGetRequest (options: CommonRequestParams & {
45 const req = request(options.url).get(options.path)
47 if (options.query) req.query(options.query)
48 if (options.rawQuery) req.query(options.rawQuery)
50 return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
53 function makeHTMLRequest (url: string, path: string) {
54 return makeGetRequest({
58 expectedStatus: HttpStatusCode.OK_200
62 function makeActivityPubGetRequest (url: string, path: string, expectedStatus = HttpStatusCode.OK_200) {
63 return makeGetRequest({
67 accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8'
71 function makeDeleteRequest (options: CommonRequestParams & {
75 const req = request(options.url).delete(options.path)
77 if (options.query) req.query(options.query)
78 if (options.rawQuery) req.query(options.rawQuery)
80 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
83 function makeUploadRequest (options: CommonRequestParams & {
84 method?: 'POST' | 'PUT'
86 fields: { [ fieldName: string ]: any }
87 attaches?: { [ attachName: string ]: any | any[] }
89 let req = options.method === 'PUT'
90 ? request(options.url).put(options.path)
91 : request(options.url).post(options.path)
93 req = buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
95 buildFields(req, options.fields)
97 Object.keys(options.attaches || {}).forEach(attach => {
98 const value = options.attaches[attach]
101 if (Array.isArray(value)) {
102 req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1])
104 req.attach(attach, buildAbsoluteFixturePath(value))
111 function makePostBodyRequest (options: CommonRequestParams & {
112 fields?: { [ fieldName: string ]: any }
114 const req = request(options.url).post(options.path)
115 .send(options.fields)
117 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
120 function makePutBodyRequest (options: {
124 fields: { [ fieldName: string ]: any }
125 expectedStatus?: HttpStatusCode
127 const req = request(options.url).put(options.path)
128 .send(options.fields)
130 return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options })
133 function decodeQueryString (path: string) {
134 return decode(path.split('?')[1])
137 function unwrapBody <T> (test: request.Test): Promise<T> {
138 return test.then(res => res.body)
141 function unwrapText (test: request.Test): Promise<string> {
142 return test.then(res => res.text)
145 function unwrapBodyOrDecodeToJSON <T> (test: request.Test): Promise<T> {
146 return test.then(res => {
147 if (res.body instanceof Buffer) {
149 return JSON.parse(new TextDecoder().decode(res.body))
151 console.error('Cannot decode JSON.', res.body)
160 function unwrapTextOrDecode (test: request.Test): Promise<string> {
161 return test.then(res => res.text || new TextDecoder().decode(res.body))
164 // ---------------------------------------------------------------------------
175 makeActivityPubGetRequest,
178 unwrapBodyOrDecodeToJSON,
182 // ---------------------------------------------------------------------------
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)
194 Object.keys(options.headers || {}).forEach(name => {
195 req.set(name, options.headers[name])
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')
209 function buildFields (req: request.Test, fields: { [ fieldName: string ]: any }, namespace?: string) {
214 for (const key of Object.keys(fields)) {
215 if (namespace) formKey = `${namespace}[${key}]`
218 if (fields[key] === undefined) continue
220 if (Array.isArray(fields[key]) && fields[key].length === 0) {
225 if (fields[key] !== null && typeof fields[key] === 'object') {
226 buildFields(req, fields[key], formKey)
228 req.field(formKey, fields[key])