requests.ts 6.2 KB

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