HATEOAS-first backend framework.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

193 satır
5.2 KiB

  1. import {IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders, request, RequestOptions} from 'http';
  2. import {Method} from '../src/backend/common';
  3. import {DataSource} from '../src/backend/data-source';
  4. interface ClientParams {
  5. method: Method;
  6. path: string;
  7. headers?: IncomingHttpHeaders;
  8. body?: unknown;
  9. }
  10. type ResponseBody = Buffer | string | object;
  11. export interface TestClient {
  12. (params: ClientParams): Promise<[IncomingMessage, ResponseBody?]>;
  13. acceptMediaType(mediaType: string): this;
  14. acceptLanguage(language: string): this;
  15. acceptCharset(charset: string): this;
  16. contentType(mediaType: string): this;
  17. contentCharset(charset: string): this;
  18. }
  19. export const createTestClient = (options: Omit<RequestOptions, 'method' | 'path'>): TestClient => {
  20. const additionalHeaders: OutgoingHttpHeaders = {};
  21. const client = (params: ClientParams) => new Promise<[IncomingMessage, ResponseBody?]>((resolve, reject) => {
  22. const {
  23. ...etcAdditionalHeaders
  24. } = additionalHeaders;
  25. const headers: OutgoingHttpHeaders = {
  26. ...(options.headers ?? {}),
  27. ...etcAdditionalHeaders,
  28. };
  29. let contentTypeHeader: string | undefined;
  30. if (typeof params.body !== 'undefined') {
  31. contentTypeHeader = headers['content-type'] = params.headers?.['content-type'] ?? 'application/json';
  32. }
  33. const req = request({
  34. ...options,
  35. method: params.method,
  36. path: params.path,
  37. headers,
  38. });
  39. req.on('response', (res) => {
  40. res.on('error', (err) => {
  41. reject(err);
  42. });
  43. let resBuffer: Buffer | undefined;
  44. res.on('data', (c) => {
  45. resBuffer = (
  46. typeof resBuffer === 'undefined'
  47. ? Buffer.from(c)
  48. : Buffer.concat([resBuffer, c])
  49. );
  50. });
  51. res.on('close', () => {
  52. const acceptHeader = Array.isArray(headers['accept']) ? headers['accept'].join('; ') : headers['accept'];
  53. const contentTypeBase = acceptHeader ?? 'application/octet-stream';
  54. const [type, subtype] = contentTypeBase.split('/');
  55. const allSubtypes = subtype.split('+');
  56. if (typeof resBuffer !== 'undefined') {
  57. if (allSubtypes.includes('json')) {
  58. const acceptCharset = (
  59. Array.isArray(headers['accept-charset'])
  60. ? headers['accept-charset'].join('; ')
  61. : headers['accept-charset']
  62. ) as BufferEncoding | undefined;
  63. resolve([res, JSON.parse(resBuffer.toString(acceptCharset ?? 'utf-8'))]);
  64. return;
  65. }
  66. if (type === 'text') {
  67. const acceptCharset = (
  68. Array.isArray(headers['accept-charset'])
  69. ? headers['accept-charset'].join('; ')
  70. : headers['accept-charset']
  71. ) as BufferEncoding | undefined;
  72. resolve([res, resBuffer.toString(acceptCharset ?? 'utf-8')]);
  73. return;
  74. }
  75. resolve([res, resBuffer]);
  76. return;
  77. }
  78. resolve([res]);
  79. });
  80. });
  81. req.on('error', (err) => {
  82. reject(err);
  83. })
  84. if (typeof params.body !== 'undefined') {
  85. const theContentTypeHeader = Array.isArray(contentTypeHeader) ? contentTypeHeader.join('; ') : contentTypeHeader?.toString();
  86. const contentTypeAll = theContentTypeHeader ?? 'application/octet-stream';
  87. const [contentTypeBase, ...contentTypeParams] = contentTypeAll.split(';').map((s) => s.replace(/\s+/g, '').trim());
  88. const charsetParam = contentTypeParams.find((s) => s.startsWith('charset='));
  89. const charset = charsetParam?.split('=')?.[1] as BufferEncoding | undefined;
  90. const [, subtype] = contentTypeBase.split('/');
  91. const allSubtypes = subtype.split('+');
  92. req.write(
  93. allSubtypes.includes('json')
  94. ? JSON.stringify(params.body)
  95. : Buffer.from(params.body?.toString() ?? '', contentTypeBase === 'text' ? charset : undefined)
  96. );
  97. }
  98. req.end();
  99. });
  100. client.acceptMediaType = function acceptMediaType(mediaType: string) {
  101. additionalHeaders['accept'] = mediaType;
  102. return this;
  103. };
  104. client.acceptLanguage = function acceptLanguage(language: string) {
  105. additionalHeaders['accept-language'] = language;
  106. return this;
  107. };
  108. client.acceptCharset = function acceptCharset(charset: string) {
  109. additionalHeaders['accept-charset'] = charset;
  110. return this;
  111. };
  112. client.contentType = function contentType(mediaType: string) {
  113. additionalHeaders['content-type'] = mediaType;
  114. return this;
  115. };
  116. client.contentCharset = function contentCharset(charset: string) {
  117. additionalHeaders['content-type'] = `${additionalHeaders['content-type']}; charset="${charset}"`;
  118. return this;
  119. };
  120. return client;
  121. };
  122. export class DummyError extends Error {}
  123. export class DummyDataSource implements DataSource {
  124. private resource?: { dataSource?: unknown };
  125. create(): Promise<never> {
  126. throw new DummyError();
  127. }
  128. delete(): Promise<never> {
  129. throw new DummyError();
  130. }
  131. emplace(): Promise<never> {
  132. throw new DummyError();
  133. }
  134. getById(): Promise<never> {
  135. throw new DummyError();
  136. }
  137. newId(): Promise<never> {
  138. throw new DummyError();
  139. }
  140. getMultiple(): Promise<never> {
  141. throw new DummyError();
  142. }
  143. getSingle(): Promise<never> {
  144. throw new DummyError();
  145. }
  146. getTotalCount(): Promise<never> {
  147. throw new DummyError();
  148. }
  149. async initialize(): Promise<void> {}
  150. patch(): Promise<never> {
  151. throw new DummyError();
  152. }
  153. prepareResource(rr: unknown) {
  154. this.resource = rr as unknown as { dataSource: DummyDataSource };
  155. this.resource.dataSource = this;
  156. }
  157. }