import {IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders, request, RequestOptions} from 'http'; import {Method} from '../src/backend/common'; import {DataSource} from '../src/backend/data-source'; interface ClientParams { method: Method; path: string; headers?: IncomingHttpHeaders; body?: unknown; } type ResponseBody = Buffer | string | object; export interface TestClient { (params: ClientParams): Promise<[IncomingMessage, ResponseBody?]>; acceptMediaType(mediaType: string): this; acceptLanguage(language: string): this; acceptCharset(charset: string): this; contentType(mediaType: string): this; contentCharset(charset: string): this; } export const createTestClient = (options: Omit): TestClient => { const additionalHeaders: OutgoingHttpHeaders = {}; const client = (params: ClientParams) => new Promise<[IncomingMessage, ResponseBody?]>((resolve, reject) => { const { ...etcAdditionalHeaders } = additionalHeaders; const headers: OutgoingHttpHeaders = { ...(options.headers ?? {}), ...etcAdditionalHeaders, }; let contentTypeHeader: string | undefined; if (typeof params.body !== 'undefined') { contentTypeHeader = headers['content-type'] = params.headers?.['content-type'] ?? 'application/json'; } const req = request({ ...options, method: params.method, path: params.path, headers, }); req.on('response', (res) => { res.on('error', (err) => { reject(err); }); let resBuffer: Buffer | undefined; res.on('data', (c) => { resBuffer = ( typeof resBuffer === 'undefined' ? Buffer.from(c) : Buffer.concat([resBuffer, c]) ); }); res.on('close', () => { const acceptHeader = Array.isArray(headers['accept']) ? headers['accept'].join('; ') : headers['accept']; const contentTypeBase = acceptHeader ?? 'application/octet-stream'; const [type, subtype] = contentTypeBase.split('/'); const allSubtypes = subtype.split('+'); if (typeof resBuffer !== 'undefined') { if (allSubtypes.includes('json')) { const acceptCharset = ( Array.isArray(headers['accept-charset']) ? headers['accept-charset'].join('; ') : headers['accept-charset'] ) as BufferEncoding | undefined; resolve([res, JSON.parse(resBuffer.toString(acceptCharset ?? 'utf-8'))]); return; } if (type === 'text') { const acceptCharset = ( Array.isArray(headers['accept-charset']) ? headers['accept-charset'].join('; ') : headers['accept-charset'] ) as BufferEncoding | undefined; resolve([res, resBuffer.toString(acceptCharset ?? 'utf-8')]); return; } resolve([res, resBuffer]); return; } resolve([res]); }); }); req.on('error', (err) => { reject(err); }) if (typeof params.body !== 'undefined') { const theContentTypeHeader = Array.isArray(contentTypeHeader) ? contentTypeHeader.join('; ') : contentTypeHeader?.toString(); const contentTypeAll = theContentTypeHeader ?? 'application/octet-stream'; const [contentTypeBase, ...contentTypeParams] = contentTypeAll.split(';').map((s) => s.replace(/\s+/g, '').trim()); const charsetParam = contentTypeParams.find((s) => s.startsWith('charset=')); const charset = charsetParam?.split('=')?.[1] as BufferEncoding | undefined; const [, subtype] = contentTypeBase.split('/'); const allSubtypes = subtype.split('+'); req.write( allSubtypes.includes('json') ? JSON.stringify(params.body) : Buffer.from(params.body?.toString() ?? '', contentTypeBase === 'text' ? charset : undefined) ); } req.end(); }); client.acceptMediaType = function acceptMediaType(mediaType: string) { additionalHeaders['accept'] = mediaType; return this; }; client.acceptLanguage = function acceptLanguage(language: string) { additionalHeaders['accept-language'] = language; return this; }; client.acceptCharset = function acceptCharset(charset: string) { additionalHeaders['accept-charset'] = charset; return this; }; client.contentType = function contentType(mediaType: string) { additionalHeaders['content-type'] = mediaType; return this; }; client.contentCharset = function contentCharset(charset: string) { additionalHeaders['content-type'] = `${additionalHeaders['content-type']}; charset="${charset}"`; return this; }; return client; }; export class DummyError extends Error {} export class DummyDataSource implements DataSource { private resource?: { dataSource?: unknown }; create(): Promise { throw new DummyError(); } delete(): Promise { throw new DummyError(); } emplace(): Promise { throw new DummyError(); } getById(): Promise { throw new DummyError(); } newId(): Promise { throw new DummyError(); } getMultiple(): Promise { throw new DummyError(); } getSingle(): Promise { throw new DummyError(); } getTotalCount(): Promise { throw new DummyError(); } async initialize(): Promise {} patch(): Promise { throw new DummyError(); } prepareResource(rr: unknown) { this.resource = rr as unknown as { dataSource: DummyDataSource }; this.resource.dataSource = this; } }