stream#Stream TypeScript Examples

The following examples show how to use stream#Stream. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From axios-source-analysis with MIT License 7 votes vote down vote up
/**
 * Determine if a value is a Stream
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Stream, otherwise false
 */
export function isStream(val: any): val is Stream {
  // @ts-ignore
  return isObject(val) && isFunction(val.pipe);
}
Example #2
Source File: index.ts    From Dimensions with MIT License 6 votes vote down vote up
/**
   * Executes the given command string in the agent's container and attaches stdin, stdout, and stderr accordingly
   * @param command - the command to execute in the container
   */
  async containerSpawn(
    command: string,
    workingDir = '/'
  ): Promise<Agent.ContainerExecData> {
    const exec = await this.container.exec({
      Cmd: ['/bin/sh', '-c', command],
      AttachStdin: true,
      AttachStdout: true,
      AttachStderr: true,
      WorkingDir: workingDir,
    });

    const stream = await exec.start({ stdin: true, hijack: true });
    const instream = new Stream.PassThrough();
    const outstream = new Stream.PassThrough();
    const errstream = new Stream.PassThrough();
    instream.pipe(stream);
    this.container.modem.demuxStream(stream, outstream, errstream);

    return {
      in: instream,
      out: outstream,
      err: errstream,
      stream,
      exec,
    };
  }
Example #3
Source File: react.tsx    From farrow with MIT License 6 votes vote down vote up
renderToNodeStream = <T extends JSX.Element>(element: T, options?: ReactResponseOptions) => {
  const contentStream = ReactDOMServer.renderToNodeStream(element)
  const docType = options?.docType ?? defaultDocType

  const docTypeStream = new Stream.Readable({
    read() {
      this.push(`${docType}\n`)
      this.push(null)
    },
  })

  const stream = new (MultiStream as any)([docTypeStream, contentStream])

  return Response.type('html').stream(stream)
}
Example #4
Source File: http.ts    From farrow with MIT License 6 votes vote down vote up
handleStream = (res: ServerResponse, stream: Stream) => {
  return new Promise<void>((resolve, reject) => {
    stream.once('error', reject)
    stream.pipe(res)
    onfinish(res, (error) => {
      if (error) {
        reject(error)
      } else {
        resolve()
      }
      destroy(stream)
    })
  })
}
Example #5
Source File: openStackSwift.ts    From backstage with Apache License 2.0 6 votes vote down vote up
streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
  return new Promise((resolve, reject) => {
    try {
      const chunks: any[] = [];
      stream.on('data', chunk => chunks.push(chunk));
      stream.on('error', reject);
      stream.on('end', () => resolve(Buffer.concat(chunks)));
    } catch (e) {
      throw new ForwardedError('Unable to parse the response data', e);
    }
  });
}
Example #6
Source File: workflow-helper.ts    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
/**
 * Compose a pod logs stream handler - i.e. a stream handler returns a stream
 * containing the pod logs.
 * @param handler a function that returns a stream.
 * @param fallback a fallback function that returns a stream if the initial handler
 * fails.
 */
export function composePodLogsStreamHandler<T = Stream>(
  handler: (podName: string, namespace?: string) => Promise<T>,
  fallback?: (podName: string, namespace?: string) => Promise<T>,
) {
  return async (podName: string, namespace?: string) => {
    try {
      return await handler(podName, namespace);
    } catch (err) {
      if (fallback) {
        return await fallback(podName, namespace);
      }
      console.warn(err);
      throw err;
    }
  };
}
Example #7
Source File: app.service.ts    From nestjs-file-streaming with MIT License 6 votes vote down vote up
async upload(request: Request): Promise<{ id: string }> {
    return new Promise((resolve, reject) => {
      try {
        request.multipart(
          (field, file: Stream, filename, encoding, mimetype) => {
            const id = new ObjectId()
            const uploadStream = this.bucket.openUploadStreamWithId(
              id,
              filename,
              {
                contentType: mimetype,
              },
            )

            file.on('end', () => {
              resolve({
                id: uploadStream.id.toString(),
              })
            })

            file.pipe(uploadStream)
          },
          (err) => {
            console.error(err)
            reject(new ServiceUnavailableException())
          },
        )
      } catch (e) {
        console.error(e)
        reject(new ServiceUnavailableException())
      }
    })
  }
Example #8
Source File: file-view.ts    From malagu with MIT License 6 votes vote down vote up
protected streamDownload(response: Response, stream: Stream): void {
        stream.on('error', error => {
            this.handleError(response, error);
        });
        response.on('error', error => {
            this.handleError(response, error);
        });
        response.on('close', () => {
            if (typeof (stream as any).destroy === 'function') {
                (stream as any).destroy();
            }
        });
        stream.pipe(response);
    }
Example #9
Source File: index.ts    From electron-request with MIT License 5 votes vote down vote up
constructor(body: Stream, options: ResponseOptions) {
    this.body = body;
    this.config = options;
    this.disturbed = false;
  }
Example #10
Source File: index.ts    From electron-request with MIT License 5 votes vote down vote up
private body: Stream;
Example #11
Source File: index.ts    From electron-request with MIT License 5 votes vote down vote up
private consumeResponse = (): Promise<Buffer> => {
    const { requestURL, size } = this.config;

    if (this.disturbed) {
      return Promise.reject(new Error(`Response used already for: ${requestURL}`));
    }

    this.disturbed = true;

    // body is null
    if (this.body === null) {
      return Promise.resolve(Buffer.alloc(0));
    }

    // body is string
    if (typeof this.body === 'string') {
      return Promise.resolve(Buffer.from(this.body));
    }

    // body is blob
    if (this.body instanceof Blob) {
      return Promise.resolve(this.body.content);
    }

    // body is buffer
    if (Buffer.isBuffer(this.body)) {
      return Promise.resolve(this.body);
    }

    if (!(this.body instanceof Stream)) {
      return Promise.resolve(Buffer.alloc(0));
    }

    // body is stream
    // get ready to actually consume the body
    const accum: Buffer[] = [];
    let accumBytes = 0;
    let abort = false;

    return new Promise((resolve, reject) => {
      // handle stream error, such as incorrect content-encoding
      this.body.on(RESPONSE_EVENT.ERROR, (err) => {
        reject(
          new Error(`Invalid response body while trying to fetch ${requestURL}: ${err.message}`),
        );
      });

      this.body.on(RESPONSE_EVENT.DATA, (chunk: Buffer) => {
        if (abort || chunk === null) {
          return;
        }

        if (size && accumBytes + chunk.length > size) {
          abort = true;
          reject(new Error(`Content size at ${requestURL} over limit: ${size}`));
          this.body.emit(RESPONSE_EVENT.CANCEL_REQUEST);
          return;
        }

        accumBytes += chunk.length;
        accum.push(chunk);
      });

      this.body.on(RESPONSE_EVENT.END, () => {
        if (abort) {
          return;
        }
        resolve(Buffer.concat(accum, accumBytes));
      });
    });
  };
Example #12
Source File: Response.ts    From ZenTS with MIT License 5 votes vote down vote up
public stream(stream: Stream): this {
    this._body = stream
    this.bodyType = RESPONSE_BODY_TYPE.STREAM

    return this
  }
Example #13
Source File: object-storage-service.ts    From malagu with MIT License 5 votes vote down vote up
async getStream(request: GetObjectRequest): Promise<Stream> {
        const service = await this.getRawCloudService();
        const { bucket, key } = request;
        return service.getObjectStream({ Bucket: bucket, Key: key, Region: this.region });
    }
Example #14
Source File: object-storage-protocol.ts    From malagu with MIT License 5 votes vote down vote up
abstract getStream(request: GetObjectRequest): Promise<Stream>;
Example #15
Source File: examples.ts    From eventcatalog with MIT License 5 votes vote down vote up
pipeline = promisify(Stream.pipeline)
Example #16
Source File: xoauth2.d.ts    From dropify with MIT License 5 votes vote down vote up
declare class XOAuth2 extends Stream {
    options: XOAuth2.Options;
    logger: shared.Logger;
    accessToken: string | false;
    expires: number;

    constructor(options?: XOAuth2.Options, logger?: shared.Logger);

    /** Returns or generates (if previous has expired) a XOAuth2 token */
    getToken(renew: boolean, callback: (err: Error | null, accessToken: string) => void): void;

    /** Updates token values */
    updateToken(accessToken: string, timeout: s): XOAuth2.Token;

    /** Generates a new XOAuth2 token with the credentials provided at initialization */
    generateToken(callback: (err: Error | null, accessToken: string) => void): void;

    /** Converts an access_token and user id into a base64 encoded XOAuth2 token */
    buildXOAuth2Token(accessToken: string): string;

    /**
     * Custom POST request handler.
     * This is only needed to keep paths short in Windows – usually this module
     * is a dependency of a dependency and if it tries to require something
     * like the request module the paths get way too long to handle for Windows.
     * As we do only a simple POST request we do not actually require complicated
     * logic support (no redirects, no nothing) anyway.
     */
    postRequest(
        url: string,
        payload: string | Buffer | Readable | { [key: string]: string },
        params: XOAuth2.RequestParams,
        callback: (err: Error | null, buf: Buffer) => void
    ): void;

    /** Encodes a buffer or a string into Base64url format */
    toBase64URL(data: Buffer | string): string;

    /** Creates a JSON Web Token signed with RS256 (SHA256 + RSA) */
    jwtSignRS256(payload: object): string;

    addListener(event: 'error', listener: (err: Error) => void): this;
    addListener(event: 'token', listener: (token: XOAuth2.Token) => void): this;

    emit(event: 'error', error: Error): boolean;
    emit(event: 'token', token: XOAuth2.Token): boolean;

    on(event: 'error', listener: (err: Error) => void): this;
    on(event: 'token', listener: (token: XOAuth2.Token) => void): this;

    once(event: 'error', listener: (err: Error) => void): this;
    once(event: 'token', listener: (token: XOAuth2.Token) => void): this;

    prependListener(event: 'error', listener: (err: Error) => void): this;
    prependListener(event: 'end', listener: (token: XOAuth2.Token) => void): this;

    prependOnceListener(event: 'error', listener: (err: Error) => void): this;
    prependOnceListener(event: 'end', listener: (token: XOAuth2.Token) => void): this;

    listeners(event: 'error'): Array<(err: Error) => void>;
    listeners(event: 'end'): Array<(token: XOAuth2.Token) => void>;
}
Example #17
Source File: index.d.ts    From amazon-kinesis-video-streams-webrtc-sdk-js-with-amazon-cognito with MIT No Attribution 5 votes vote down vote up
declare function StreamEvents<StreamType extends Stream>(stream: StreamType)
  : StreamType;
Example #18
Source File: index.d.ts    From amazon-kinesis-video-streams-webrtc-sdk-js-with-amazon-cognito with MIT No Attribution 5 votes vote down vote up
getStream: {
	/**
	Get the `stream` as a string.

	@returns A promise that resolves when the end event fires on the stream, indicating that there is no more data to be read. The stream is switched to flowing mode.

	@example
	```
	import * as fs from 'fs';
	import getStream = require('get-stream');

	(async () => {
		const stream = fs.createReadStream('unicorn.txt');

		console.log(await getStream(stream));
		//               ,,))))))));,
		//            __)))))))))))))),
		// \|/       -\(((((''''((((((((.
		// -*-==//////((''  .     `)))))),
		// /|\      ))| o    ;-.    '(((((                                  ,(,
		//          ( `|    /  )    ;))))'                               ,_))^;(~
		//             |   |   |   ,))((((_     _____------~~~-.        %,;(;(>';'~
		//             o_);   ;    )))(((` ~---~  `::           \      %%~~)(v;(`('~
		//                   ;    ''''````         `:       `:::|\,__,%%    );`'; ~
		//                  |   _                )     /      `:|`----'     `-'
		//            ______/\/~    |                 /        /
		//          /~;;.____/;;'  /          ___--,-(   `;;;/
		//         / //  _;______;'------~~~~~    /;;/\    /
		//        //  | |                        / ;   \;;,\
		//       (<_  | ;                      /',/-----'  _>
		//        \_| ||_                     //~;~~~~~~~~~
		//            `\_|                   (,~~
		//                                    \~\
		//                                     ~~
	})();
	```
	*/
	(stream: Stream, options?: getStream.OptionsWithEncoding): Promise<string>;

	/**
	Get the `stream` as a buffer.

	It honors the `maxBuffer` option as above, but it refers to byte length rather than string length.
	*/
	buffer(
		stream: Stream,
		options?: getStream.OptionsWithEncoding
	): Promise<Buffer>;

	/**
	Get the `stream` as an array of values.

	It honors both the `maxBuffer` and `encoding` options. The behavior changes slightly based on the encoding chosen:

	- When `encoding` is unset, it assumes an [object mode stream](https://nodesource.com/blog/understanding-object-streams/) and collects values emitted from `stream` unmodified. In this case `maxBuffer` refers to the number of items in the array (not the sum of their sizes).
	- When `encoding` is set to `buffer`, it collects an array of buffers. `maxBuffer` refers to the summed byte lengths of every buffer in the array.
	- When `encoding` is set to anything else, it collects an array of strings. `maxBuffer` refers to the summed character lengths of every string in the array.
	*/
	array<StreamObjectModeType = unknown>(
		stream: Stream,
		options?: getStream.Options
	): Promise<StreamObjectModeType[]>;
	array(
		stream: Stream,
		options: getStream.OptionsWithEncoding<'buffer'>
	): Promise<Buffer[]>;
	array(
		stream: Stream,
		options: getStream.OptionsWithEncoding<BufferEncoding>
	): Promise<string[]>;

	MaxBufferError: typeof MaxBufferErrorClass;

	// TODO: Remove this for the next major release
	default: typeof getStream;
}
Example #19
Source File: drive.ts    From drive-proxy with Apache License 2.0 4 votes vote down vote up
resolvePath = async (path: string, res): Promise<Stream> => {
  const files = google.drive('v3').files;
  await getAuth();

  let cacheStatus: 'MISS' | 'HIT' | 'PARTIAL-HIT' = 'MISS';

  const cached = get(path)?.file;
  const parts = path.split('/');
  let file =
    cached ??
    (
      await files.get({
        fileId: process.env.ROOT_FOLDER_ID,
        fields: 'id,name,mimeType,size',
      })
    ).data;

  let resolves = cached ? 0 : 1;

  if (path !== '' && !cached) {
    // Attempt to find a cached entrypoint
    let i = parts.length;
    for (; i > 0; i--) {
      const subpath = parts.slice(0, i + 1).join('/');
      const cachedPart = get(subpath)?.file;
      if (cachedPart) {
        file = cachedPart;
        break;
      }
    }

    if (file) {
      cacheStatus = 'PARTIAL-HIT';
      console.log(`[PARTIAL-HIT] ${i + 1}/${parts.length}`);
    }

    // Didn't find deeply nested
    for (; i < parts.length; i++) {
      const part = decodeURI(parts[i]);
      const subpath = parts.slice(0, i + 1).join('/');
      const cachedPart = get(subpath);

      const match = cachedPart
        ? cachedPart.file
        : (
            await files.list({
              q: `'${file.id}' in parents and name = '${part}'`,
              pageSize: 1000,
              fields: 'files(id,mimeType,name,size)',
            })
          ).data.files[0];

      if (!match) throw new Error('404 Not Found');

      if (!cachedPart) {
        resolves++;
        add(subpath, match);
      }

      if (i + 1 < parts.length) {
        if (match.mimeType === 'application/vnd.google-apps.folder') {
          file = match;
          continue;
        } else {
          throw new Error('404 Not Found');
        }
      }

      file = match;
    }
  }

  if (!cached) {
    add(path, file);
  } else if (!cacheStatus) {
    cacheStatus = 'HIT';
  }

  resolves++;

  if (file.mimeType === 'application/vnd.google-apps.folder') {
    let result = get(path)?.children;

    if (result) {
      resolves--;
      cacheStatus = 'HIT';
    } else {
      result = (
        await files.list({
          q: `'${file.id}' in parents`,
          pageSize: 1000,
          fields: 'files(id,mimeType,name,size,modifiedTime)',
        })
      ).data.files;
      add(path, file, result);
    }

    res.setHeader('X-Drive-API-Calls', resolves);
    res.setHeader('X-Drive-Cache-Status', cacheStatus);

    return Readable.from(
      `<h1>/${path}</h1><table><thead><th>Name</th><th>Modified</th><th>Size</th></thead><tbody>` +
        (path !== '/' ? '<tr><td><a href=".."/>..</a></td></tr>' : '') +
        result
          .map(
            (f) =>
              `<tr><td><a href="${
                path[0] === '/' || path === '' ? path : '/' + path
              }/${f.name}">${f.name}</a></td><td>${f.modifiedTime}</td><td>${
                f.size ? humanFileSize(Number.parseInt(f.size, 10)) : 0
              }</td></tr>`,
          )
          .join(' ') +
        '</tbody></table>',
    );
  } else {
    res.setHeader('Content-Type', file.mimeType);
    res.setHeader('Content-Length', file.size);

    const cachedData = get(path);

    if (cachedData?.data) {
      resolves--;
      cacheStatus = 'HIT';
      res.setHeader('X-Drive-Cache-Status', cacheStatus);
      res.setHeader('X-Drive-API-Calls', resolves);
      return Readable.from(cachedData.data);
    }

    res.setHeader('X-Drive-Cache-Status', cacheStatus);
    res.setHeader('X-Drive-API-Calls', resolves);

    const stream = (
      await files.get(
        {
          fileId: file.id,
          alt: 'media',
        },
        { responseType: 'stream' },
      )
    ).data as Stream;

    const fileSize = Number.parseInt(file.size, 10);
    const CACHE_MAX_FILE_SIZE: number = Number.parseInt(
      process.env.CACHE_MAX_FILE_SIZE,
      10,
    );

    if (fileSize < CACHE_MAX_FILE_SIZE) {
      const cacheBuffers: Buffer[] = [];

      stream.addListener('data', (d) => cacheBuffers.push(d));
      stream.addListener('end', () => {
        add(path, file, null, Buffer.concat(cacheBuffers));
        console.log(
          '[CACHE] Committed buffer of size ' + file.size + ' to memory cache',
        );
      });
    } else {
      console.log(
        `[CACHE] Refusing to commit buffer of size ${humanFileSize(
          fileSize,
        )}. Max is ${humanFileSize(CACHE_MAX_FILE_SIZE)}`,
      );
    }

    return stream;
  }
}
Example #20
Source File: handler.ts    From next-api-decorators with MIT License 4 votes vote down vote up
async function runMainLayer(
  this: TypedPropertyDescriptor<any>,
  target: object,
  propertyKey: string | symbol,
  originalHandler: any,
  req: NextApiRequest,
  res: NextApiResponse
): Promise<void> {
  const httpCode: number | undefined = Reflect.getMetadata(HTTP_CODE_TOKEN, target.constructor, propertyKey);
  const parameterDecorators: MetaParameter[] = (
    Reflect.getMetadata(PARAMETER_TOKEN, target.constructor, propertyKey) ?? []
  ).sort((a: MetaParameter, b: MetaParameter) => a.index - b.index);
  const classHeaders: Map<string, string> | undefined = Reflect.getMetadata(HEADER_TOKEN, target.constructor);
  const methodHeaders: Map<string, string> | undefined = Reflect.getMetadata(
    HEADER_TOKEN,
    target.constructor,
    propertyKey
  );
  const parameterTypes: ClassConstructor<any>[] = Reflect.getMetadata('design:paramtypes', target, propertyKey);
  const isDownloadable: boolean = Reflect.getMetadata(HTTP_DOWNLOAD_TOKEN, target.constructor, propertyKey) ?? false;

  const parameters = await Promise.all(
    parameterDecorators.map(async ({ location, name, pipes, index, fn }) => {
      if (location === 'custom') {
        return fn?.call(null, req);
      }

      const paramType =
        index < parameterTypes.length &&
        typeof parameterTypes[index] === 'function' &&
        /^class\s/.test(Function.prototype.toString.call(parameterTypes[index]))
          ? parameterTypes[index]
          : undefined;

      let returnValue = getParameterValue(req, res, {
        location,
        name,
        index
      });

      if (pipes && pipes.length) {
        for (const pipeFn of pipes) {
          returnValue = pipeFn.name
            ? // Bare pipe function. i.e: `ParseNumberPipe`
              await pipeFn.call(null, null).call(null, returnValue, { name, metaType: paramType })
            : // Pipe with options. i.e: `ParseNumberPipe({ nullable: false })`
              await pipeFn.call(null, returnValue, {
                name,
                metaType: paramType
              });
        }
      }

      return returnValue;
    })
  );

  classHeaders?.forEach((value, name) => res.setHeader(name, value));
  methodHeaders?.forEach((value, name) => res.setHeader(name, value));

  const returnValue = await originalHandler.call(this, ...parameters);

  if (returnValue instanceof ServerResponse || isResponseSent(res)) {
    return;
  }

  res.status(httpCode ?? (returnValue != null ? 200 : 204));

  if (returnValue instanceof Stream) {
    returnValue.pipe(res);
  } else if (
    isDownloadable &&
    typeof returnValue === 'object' &&
    'filename' in returnValue &&
    'contents' in returnValue
  ) {
    res.setHeader('Content-Disposition', `attachment; filename="${returnValue.filename}"`);

    if ('contentType' in returnValue) {
      res.setHeader('Content-Type', returnValue.contentType);
    }

    if (returnValue.contents instanceof Stream) {
      returnValue.contents.pipe(res);
    } else {
      res.send(returnValue.contents);
    }
  } else {
    res.send(returnValue ?? null);
  }
}
Example #21
Source File: NativeRequestClient.ts    From electron-request with MIT License 4 votes vote down vote up
public send = () => {
    const {
      method,
      followRedirect,
      maxRedirectCount,
      requestURL,
      parsedURL,
      size,
      timeout,
      body: requestBody,
    } = this.options;

    /** Create NodeJS request */
    const clientRequest = this.createRequest();
    /** Cancel NodeJS request */
    const cancelRequest = () => {
      // In NodeJS, `request.abort()` is deprecated since v14.1.0. Use `request.destroy()` instead.
      clientRequest.destroy();
    };
    /** Write body to NodeJS request */
    const writeToRequest = () => {
      if (requestBody === null) {
        clientRequest.end();
      } else if (requestBody instanceof Stream) {
        requestBody.pipe(clientRequest);
      } else {
        clientRequest.write(requestBody);
        clientRequest.end();
      }
    };
    /** Bind request event */
    const bindRequestEvent = (
      onFulfilled: (value: Response | PromiseLike<Response>) => void,
      onRejected: (reason: Error) => void,
    ) => {
      /** Set NodeJS request timeout */
      if (timeout) {
        clientRequest.setTimeout(timeout, () => {
          onRejected(new Error(`NodeJS request timeout in ${timeout} ms`));
        });
      }

      /** Bind NodeJS request error event */
      clientRequest.on(REQUEST_EVENT.ERROR, onRejected);

      /** Bind NodeJS request abort event */
      clientRequest.on(REQUEST_EVENT.ABORT, () => {
        onRejected(new Error('NodeJS request was aborted by the server'));
      });

      /** Bind NodeJS request response event */
      clientRequest.on(REQUEST_EVENT.RESPONSE, (res) => {
        const { statusCode = 200, headers: responseHeaders } = res;
        const headers = new Headers(responseHeaders);

        if (isRedirect(statusCode) && followRedirect) {
          if (maxRedirectCount && this.redirectCount >= maxRedirectCount) {
            onRejected(new Error(`Maximum redirect reached at: ${requestURL}`));
          }

          if (!headers.get(HEADER_MAP.LOCATION)) {
            onRejected(new Error(`Redirect location header missing at: ${requestURL}`));
          }

          if (
            statusCode === 303 ||
            ((statusCode === 301 || statusCode === 302) && method === METHOD_MAP.POST)
          ) {
            this.options.method = METHOD_MAP.GET;
            this.options.body = null;
            this.options.headers.delete(HEADER_MAP.CONTENT_LENGTH);
          }

          this.redirectCount += 1;
          this.options.parsedURL = new URL(
            String(headers.get(HEADER_MAP.LOCATION)),
            parsedURL.toString(),
          );
          onFulfilled(this.send());
        }

        const pumpCallback = (error: NodeJS.ErrnoException | null) => {
          if (error !== null) {
            onRejected(error);
          }
        };

        let responseBody = pump(res, new PassThrough(), pumpCallback);
        responseBody.on(RESPONSE_EVENT.CANCEL_REQUEST, cancelRequest);

        const resolveResponse = () => {
          onFulfilled(
            new ResponseImpl(responseBody, {
              requestURL,
              statusCode,
              headers,
              size,
            }),
          );
        };
        const codings = headers.get(HEADER_MAP.CONTENT_ENCODING);
        if (
          method !== METHOD_MAP.HEAD &&
          codings !== null &&
          statusCode !== 204 &&
          statusCode !== 304
        ) {
          switch (codings) {
            case COMPRESSION_TYPE.BR:
              responseBody = pump(responseBody, zlib.createBrotliDecompress(), pumpCallback);
              break;

            case COMPRESSION_TYPE.GZIP:
            case `x-${COMPRESSION_TYPE.GZIP}`:
              responseBody = pump(responseBody, zlib.createGunzip(), pumpCallback);
              break;

            case COMPRESSION_TYPE.DEFLATE:
            case `x-${COMPRESSION_TYPE.DEFLATE}`:
              pump(res, new PassThrough(), pumpCallback).once('data', (chunk) => {
                // see http://stackoverflow.com/questions/37519828
                // eslint-disable-next-line no-bitwise
                if ((chunk[0] & 0x0f) === 0x08) {
                  responseBody = pump(responseBody, zlib.createInflate(), pumpCallback);
                } else {
                  responseBody = pump(responseBody, zlib.createInflateRaw(), pumpCallback);
                }
                resolveResponse();
              });
              return;
            default:
              break;
          }
        }
        resolveResponse();
      });
    };

    return new Promise<Response>((resolve, reject) => {
      const onRejected = (reason: Error) => {
        cancelRequest();
        reject(reason);
      };
      bindRequestEvent(resolve, onRejected);
      writeToRequest();
    });
  };
Example #22
Source File: ElectronRequestClient.ts    From electron-request with MIT License 4 votes vote down vote up
public send = async () => {
    const {
      method,
      followRedirect,
      maxRedirectCount,
      requestURL,
      parsedURL,
      size,
      username,
      password,
      timeout,
      body: requestBody,
    } = this.options;

    /** Create electron request */
    const clientRequest = await this.createRequest();
    /** Cancel electron request */
    const cancelRequest = () => {
      // In electron, `request.destroy()` does not send abort to server
      clientRequest.abort();
    };
    /** Write body to electron request */
    const writeToRequest = () => {
      if (requestBody === null) {
        clientRequest.end();
      } else if (requestBody instanceof Stream) {
        // TODO remove as
        requestBody.pipe(new PassThrough()).pipe(clientRequest as unknown as Writable);
      } else {
        clientRequest.write(requestBody);
        clientRequest.end();
      }
    };
    /** Bind electron request event */
    const bindRequestEvent = (
      onFulfilled: (value: Response | PromiseLike<Response>) => void,
      onRejected: (reason: Error) => void,
    ) => {
      /** Set electron request timeout */
      if (timeout) {
        this.timeoutId = setTimeout(() => {
          onRejected(new Error(`Electron request timeout in ${timeout} ms`));
        }, timeout);
      }

      /** Bind electron request error event */
      clientRequest.on(REQUEST_EVENT.ERROR, onRejected);

      /** Bind electron request abort event */
      clientRequest.on(REQUEST_EVENT.ABORT, () => {
        onRejected(new Error('Electron request was aborted by the server'));
      });

      /** Bind electron request login event */
      clientRequest.on(REQUEST_EVENT.LOGIN, (authInfo, callback) => {
        if (username && password) {
          callback(username, password);
        } else {
          onRejected(
            new Error(`Login event received from ${authInfo.host} but no credentials provided`),
          );
        }
      });

      /** Bind electron request response event */
      clientRequest.on(REQUEST_EVENT.RESPONSE, (res) => {
        this.clearRequestTimeout();

        const { statusCode = 200, headers: responseHeaders } = res;
        const headers = new Headers(responseHeaders);

        if (isRedirect(statusCode) && followRedirect) {
          if (maxRedirectCount && this.redirectCount >= maxRedirectCount) {
            onRejected(new Error(`Maximum redirect reached at: ${requestURL}`));
          }

          if (!headers.get(HEADER_MAP.LOCATION)) {
            onRejected(new Error(`Redirect location header missing at: ${requestURL}`));
          }

          if (
            statusCode === 303 ||
            ((statusCode === 301 || statusCode === 302) && method === METHOD_MAP.POST)
          ) {
            this.options.method = METHOD_MAP.GET;
            this.options.body = null;
            this.options.headers.delete(HEADER_MAP.CONTENT_LENGTH);
          }

          this.redirectCount += 1;
          this.options.parsedURL = new URL(
            String(headers.get(HEADER_MAP.LOCATION)),
            parsedURL.toString(),
          );
          onFulfilled(this.send());
        }

        const responseBody = pump(res, new PassThrough(), (error) => {
          if (error !== null) {
            onRejected(error);
          }
        });

        responseBody.on(RESPONSE_EVENT.CANCEL_REQUEST, cancelRequest);

        onFulfilled(
          new ResponseImpl(responseBody, {
            requestURL,
            statusCode,
            headers,
            size,
          }),
        );
      });
    };

    return new Promise<Response>((resolve, reject) => {
      const onRejected = (reason: Error) => {
        this.clearRequestTimeout();
        cancelRequest();
        reject(reason);
      };
      bindRequestEvent(resolve, onRejected);
      writeToRequest();
    });
  };
Example #23
Source File: koa.test.ts    From farrow with MIT License 4 votes vote down vote up
describe('Http', () => {
  describe('Response', () => {
    it('support text response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.text(JSON.stringify(data))
        })

      await request(createApp(http).callback())
        .get('/test')
        .expect('Content-Type', /text/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support html response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.html(JSON.stringify(data))
        })

      await request(createApp(http).callback())
        .get('/test')
        .expect('Content-Type', /html/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support json response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.json(data)
        })

      await request(createApp(http).callback())
        .get('/test')
        .set('Accept', 'application/json')
        .expect('Content-Type', /json/)
        .expect(200, {
          pathname: '/test',
        })
    })

    it('support empty response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.empty()
        })

      await request(createApp(http).callback()).get('/test').expect(204)
    })

    it('support redirecting', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.redirect('/redirected')
        })

      await request(createApp(http).callback()).get('/test').expect('Location', '/redirected').expect(302)
    })

    it('support stream response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          const stream = new Stream.Readable({
            read() {
              this.push('test stream')
              this.push(null)
            },
          })
          return Response.stream(stream)
        })

      await request(createApp(http).callback()).get('/test').expect(200, 'test stream')
    })

    it('support buffer response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.buffer(Buffer.from('test buffer'))
        })

      await request(createApp(http).callback()).get('/test').expect(200, 'test buffer')
    })

    it('support file response', async () => {
      const http = createHttp()

      const filename = path.join(__dirname, 'koa.test.ts')

      const content = await fs.promises.readFile(filename)

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.file(filename)
        })

      await request(createApp(http).callback()).get('/test').expect(200, content)
    })

    it('support string response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test string body')
        })

      await request(createApp(http).callback()).get('/test').expect(200, 'test string body')
    })

    it('support custom response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.custom(({ res, requestInfo, responseInfo }) => {
            res.end(
              JSON.stringify({
                requestInfo: {
                  ...requestInfo,
                  headers: null,
                },
                responseInfo,
              }),
            )
          })
        })

      await request(createApp(http).callback())
        .get('/test')
        .expect(
          200,
          JSON.stringify({
            requestInfo: {
              pathname: '/test',
              method: 'GET',
              query: {},
              body: null,
              headers: null,
              cookies: {},
            },
            responseInfo: {},
          }),
        )
    })

    it('should not be matched when pathname or method failed to match', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test',
          method: 'POST',
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server).get('/test').expect(404)
      await request(server).post('/test0').expect(404)
      await request(server).post('/test').expect(200, {
        pathname: '/test',
        method: 'POST',
      })
    })

    it('should respond 500 in block mode when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: true,
        },
      )

      await request(createApp(http).callback()).get('/test').expect(500)
    })

    it('should not respond 404 if matchOptions.bloack = false when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: false,
        },
      )

      await request(createApp(http).callback()).get('/test').expect(404)
    })

    it('should responding 500 when middleware throwed error', async () => {
      const http = createHttp()

      http.use((request, next) => {
        if (request.pathname === '/sync') {
          throw new Error('sync error')
        }
        return next()
      })

      http.use(async (request, next) => {
        if (request.pathname === '/async') {
          await delay(10)
          throw new Error('async error')
        }
        return next()
      })

      http.use(() => {
        return Response.json({
          ok: true,
        })
      })

      await request(createApp(http).callback()).get('/sync').expect(500)
      await request(createApp(http).callback()).get('/async').expect(500)
      await request(createApp(http).callback()).get('/').expect(200, {
        ok: true,
      })
    })

    it('support set response status', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-status',
        })
        .use(() => {
          return Response.status(302, 'abc').text('ok')
        })

      await request(server).get('/test-status').expect(302)
    })

    it('support set response headers', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-header',
        })
        .use(() => {
          return Response.header('test-header', 'header-value').text('ok')
        })

      http
        .match({
          pathname: '/test-headers',
        })
        .use(() => {
          return Response.headers({
            'test-header1': 'header1-value',
            'test-header2': 'header2-value',
          }).text('ok')
        })

      await request(server).get('/test-header').expect('test-header', 'header-value').expect(200)
      await request(server)
        .get('/test-headers')
        .expect('test-header1', 'header1-value')
        .expect('test-header2', 'header2-value')
        .expect(200)
    })

    it('support set response cookies', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-cookie',
          method: 'POST',
        })
        .use(() => {
          return Response.cookie('test-cookie', 'cookie-value').text('set cookie ok')
        })

      http
        .match({
          pathname: '/test-cookies',
          method: 'POST',
        })
        .use(() => {
          return Response.cookies({
            'test-cookie1': 'cookie1-value',
            'test-cookie2': 'cookie2-value',
          }).text('set cookies ok')
        })

      await request(server)
        .post('/test-cookie')
        .expect('set-cookie', 'test-cookie=cookie-value; path=/; httponly')
        .expect(200, 'set cookie ok')

      await request(server)
        .post('/test-cookies')
        .expect(
          'set-cookie',
          ['test-cookie1=cookie1-value; path=/; httponly', 'test-cookie2=cookie2-value; path=/; httponly'].join(','),
        )
        .expect(200, 'set cookies ok')
    })

    it('support set response vary', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.vary('origin', 'cookie').text('ok')
        })

      await request(server).get('/test-vary').expect('vary', 'origin, cookie').expect(200, 'ok')
    })

    it('support set attachment', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.attachment('test.txt').text('ok')
        })

      await request(server)
        .get('/test-vary')
        .expect('content-disposition', 'attachment; filename="test.txt"')
        .expect(200, 'ok')
    })

    it('support set content-type via mime-type', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-type',
          method: 'POST',
          body: {
            type: String,
          },
        })
        .use((request) => {
          return Response.type(request.body.type).buffer(
            Buffer.from(
              JSON.stringify({
                success: true,
                data: request.body,
              }),
            ),
          )
        })

      await request(server)
        .post('/test-type')
        .send({
          type: 'png',
        })
        .expect('Content-Type', 'image/png')

      await request(server)
        .post('/test-type')
        .send({
          type: 'jpg',
        })
        .expect('Content-Type', 'image/jpeg')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.js',
        })
        .expect('Content-Type', 'application/javascript; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.html',
        })
        .expect('Content-Type', 'text/html; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.css',
        })
        .expect('Content-Type', 'text/css; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.json',
        })
        .expect('Content-Type', 'application/json; charset=utf-8')
    })

    it('support merging response in two directions', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test-merge-01',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return response.merge(Response.text('one'))
        })
        .use(() => {
          return Response.text('two')
        })

      http
        .match({
          pathname: '/test-merge-02',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return Response.text('one').merge(response)
        })
        .use(() => {
          return Response.text('two')
        })

      await request(server).get('/test-merge-01').expect(200, 'one')
      await request(server).get('/test-merge-02').expect(200, 'two')
      await request(server).get('/test-merge-03').expect(404)
    })

    it('support serving static files', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      const dirname = path.join(__dirname, '../')

      const read = async (filename: string) => {
        const buffer = await fs.promises.readFile(path.join(dirname, filename))
        return buffer.toString()
      }

      http.serve('/static', dirname)

      await request(server)
        .get('/static/package.json')
        .expect('Content-Type', /json/)
        .expect(200, await read('package.json'))

      await request(server)
        .get('/static/README.md')
        .expect('Content-Type', /markdown/)
        .expect(200, await read('README.md'))

      await request(server)
        .get('/static/tsconfig.json')
        .expect(200, await read('tsconfig.json'))

      await request(server).get('/static/abc').expect(404)
    })

    it('should go through when the file does not exist in router.serve', async () => {
      const http = createHttp()
      const server = createApp(http).callback()
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      http.use(() => {
        return Response.text('Cheer!')
      })

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/cheer').expect(200, 'Cheer!')

      await request(server).get('/static').expect(200, 'Cheer!')
    })

    it('should only serve files under argument.dirname', async () => {
      const http = createHttp()
      const server = createApp(http).callback()
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/../bar.js').expect(404)
    })

    it('support capturing response by type', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http.capture('string', (stringBody) => {
        return Response.text(`capture: ${stringBody.value}`)
      })

      http.capture('json', (jsonBody) => {
        return Response.json({
          capture: true,
          original: jsonBody.value,
        })
      })

      http
        .match({
          pathname: '/test-text',
        })
        .use(() => {
          return Response.text('some text')
        })

      http
        .match({
          pathname: '/test-json',
        })
        .use(() => {
          return Response.json({
            data: 'ok',
          })
        })

      await request(server).get('/test-text').expect('Content-Type', /text/).expect(200, 'capture: some text')

      await request(server)
        .get('/test-json')
        .expect('Content-Type', /json/)
        .expect(200, {
          capture: true,
          original: {
            data: 'ok',
          },
        })
    })

    it('support injecting context', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = createApp(http).callback()

      http.use(() => {
        const ctx = TestContext.use()
        const { value } = ctx
        ctx.value += 1
        return Response.text(value.toString())
      })

      await request(server).get('/').expect(200, '10')
      await request(server).get('/any').expect(200, '10')
    })

    it('support async hooks', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = createApp(http).callback()

      http.use(async (request, next) => {
        await delay(1)
        const response = await next(request)
        expect(TestContext.get()).toBe(11)
        return response
      })

      http.use(() => {
        TestContext.set(TestContext.get() + 1)
        return Response.text(`${TestContext.get()}`)
      })

      await request(server).get('/').expect(200, '11')
      await request(server).get('/any').expect(200, '11')
    })

    it('should only handle GET method by default', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(404)
      await request(server).post('/test').expect(404)
      await request(server).delete('/test').expect(404)
      await request(server).put('/test').expect(404)
    })

    it('support match multiple methods', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .match({
          pathname: '/test',
          method: ['get', 'options', 'post', 'delete', 'put'],
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(200, 'test')
      await request(server).post('/test').expect(200, 'test')
      await request(server).delete('/test').expect(200, 'test')
      await request(server).put('/test').expect(200, 'test')
    })

    it('should respond 400 if request schema was not matched', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http.get('/<name:string>/<age:int>').use((request) => {
        return Response.json({
          name: request.params.name,
          age: request.params.age,
        })
      })

      await request(server).get('/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/farrow/abc').expect(400)
    })

    it('should handle schema-error if options.onSchemaError is used', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .get(
          '/catch0/<name:string>/<age:int>',
          {},
          {
            onSchemaError: (error) => {
              return Response.json({
                error,
              })
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      let count = 0

      http
        .get(
          '/catch1/<name:string>/<age:int>',
          {},
          {
            onSchemaError: () => {
              count = 1
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      await request(server).get('/catch0/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server)
        .get('/catch0/farrow/abc')
        .expect(200, {
          error: {
            path: ['params', 'age'],
            message: 'abc is not an integer',
          },
        })

      await request(server).get('/catch1/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/catch1/farrow/abc').expect(400)

      expect(count).toBe(1)
    })
  })

  describe('Request', () => {
    it('support access native req/res via useReq/useRes', async () => {
      const http = createHttp()

      http.use(() => {
        const req = useReq()
        const res = useRes()

        res.statusCode = 200
        res.end(req.url)

        return Response.custom()
      })

      await request(createApp(http).callback()).get('/test-abc').expect(200, '/test-abc')
    })

    it('support accessing request info via useRequestInfo', async () => {
      const http = createHttp()

      http.use(() => {
        const info = useRequestInfo()

        return Response.json({
          ...info,
          headers: {
            cookie: info.headers?.cookie,
            'content-type': 'application/json',
            accept: 'application/json',
            'content-length': '13',
          },
        })
      })

      await request(createApp(http).callback())
        .post('/test-abc?a=1&b=2')
        .set('Accept', 'application/json')
        .set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])
        .send({
          a: 1,
          b: 2,
        })
        .expect(200, {
          pathname: '/test-abc',
          method: 'POST',
          query: { a: '1', b: '2' },
          body: { a: 1, b: 2 },
          headers: {
            accept: 'application/json',
            cookie: 'nameOne=valueOne;nameTwo=valueTwo',
            'content-type': 'application/json',
            'content-length': '13',
          },
          cookies: { nameOne: 'valueOne', nameTwo: 'valueTwo' },
        })
    })

    it('support passing new request info to downstream', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http.use((request, next) => {
        if (request.pathname.startsWith('/new')) {
          return next({
            ...request,
            pathname: request.pathname.replace('/new', ''),
            query: {
              ...request.query,
              new: true,
            },
          })
        }
        return next()
      })

      http
        .match({
          pathname: '/test',
          query: {
            a: String,
            new: Nullable(Boolean),
          },
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server)
        .get('/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
          },
        })

      await request(server)
        .get('/new/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
            new: true,
          },
        })
    })
  })

  describe('Router', () => {
    it('should support add router', async () => {
      const http = createHttp()
      const router = Router()
      const server = createApp(http).callback()

      router
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          return Response.text(request.pathname)
        })

      http.use(router)
      http.route('/base').use(router)

      await request(server).get('/abc').expect(200, '/abc')
      await request(server).get('/base/abc').expect(200, '/abc')
    })

    it('should support using router in another router', async () => {
      const http = createHttp()
      const router0 = Router()
      const router1 = Router()
      const router2 = Router()
      const server = createApp(http).callback()

      http.use(async (request, next) => {
        const basenames = useBasenames()
        const before = basenames.value
        const response = await next(request)
        const after = basenames.value

        // should reset basenames after next(...)
        expect(before).toEqual(after)
        return response
      })

      http.route('/router0').use(router0)
      router0.route('/router1').use(router1)
      http.route('/router2').use(router2)

      http
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'http',
            prefix,
            pathname: request.pathname,
          })
        })

      router0
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router0',
            prefix,
            pathname: request.pathname,
          })
        })

      router1
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router1',
            prefix,
            pathname: request.pathname,
          })
        })

      router2.use((request) => {
        const prefix = usePrefix()
        return Response.json({
          from: 'router2',
          prefix,
          pathname: request.pathname,
        })
      })

      await request(server).get('/abc').expect(200, {
        from: 'http',
        prefix: '',
        pathname: '/abc',
      })

      await request(server).get('/router0/abc').expect(200, {
        from: 'router0',
        prefix: '/router0',
        pathname: '/abc',
      })

      await request(server).get('/router0/router1/abc').expect(200, {
        from: 'router1',
        prefix: '/router0/router1',
        pathname: '/abc',
      })

      await request(server).get('/router2').expect(200, {
        from: 'router2',
        prefix: '/router2',
        pathname: '/',
      })
    })

    it('support setting custom content-type for Response.file', async () => {
      const http = createHttp()
      const server = createApp(http).callback()
      const filename = path.join(__dirname, './fixtures/static/foo.js')
      const content = await fs.promises.readFile(filename)

      http.get('/raw').use(async () => {
        return Response.file(filename)
      })

      http.get('/text').use(async () => {
        return Response.type('text').file(filename)
      })

      await request(server)
        .get('/raw')
        .expect('Content-Type', /javascript/)
        .expect(200, content.toString())

      await request(server).get('/text').expect('Content-Type', /text/).expect(200, content.toString())
    })

    it('support remove cookies or headers', async () => {
      const http = createHttp()
      const server = createApp(http).callback()

      http
        .get(
          '/test',
          {},
          {
            block: false,
          },
        )
        .use(async (request, next) => {
          const response = await next(request)

          return response
            .cookies({
              a: '',
              b: '',
            })
            .headers({
              c: '',
              d: '',
            })
        })

      http.get('/test').use(() => {
        return Response.cookies({
          a: '1',
          b: '2',
          c: '3',
          d: '4',
        })
          .headers({
            a: '1',
            b: '2',
            c: '3',
            d: '4',
          })
          .text('OK')
      })

      await request(server)
        .get('/test')
        .expect((res) => {
          expect(res.headers['set-cookie'].length).toEqual(4)
          expect(res.headers['a']).toBe('1')
          expect(res.headers['b']).toBe('2')
        })
        .expect(200, 'OK')
    })
  })
})
Example #24
Source File: router.test.ts    From farrow with MIT License 4 votes vote down vote up
describe('Router', () => {
  it('should validating pathname & method', async () => {
    const router = Router()
    const schema = {
      pathname: '/test',
    }

    router.match(schema).use((request) => {
      return Response.json(request)
    })

    expect(() =>
      router.run({
        pathname: '/abc',
      }),
    ).toThrow()

    const result = await router.run({
      pathname: '/test',
    })

    expect(result.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/test',
      },
    })
  })

  it('should validating method & params & query & body & headers & cookies', async () => {
    const router = Router()

    const schema = {
      pathname: '/detail/:id',
      method: 'POST',
      params: {
        id: Number,
      },
      query: {
        a: Number,
        b: String,
        c: Boolean,
      },
      body: Nullable({
        a: Number,
        b: String,
        c: Boolean,
        d: {
          a: Number,
          b: String,
          c: Boolean,
        },
      }),
      headers: {
        a: Number,
        b: String,
        c: Boolean,
      },
      cookies: {
        a: Number,
        b: String,
        c: Boolean,
      },
    }

    router.match(schema).use((request) => {
      return Response.json(request)
    })

    expect(() =>
      router.run({
        pathname: '/detail/abc',
      }),
    ).toThrow()

    const request0 = {
      pathname: '/detail/123',
      method: 'POST',
      params: {
        id: 123,
      },
      query: {
        a: '1',
        b: '1',
        c: 'false',
      },
      body: null,
      headers: {
        a: '1',
        b: '1',
        c: 'false',
      },
      cookies: {
        a: '1',
        b: '1',
        c: 'false',
      },
    }

    const result0 = await router.run(request0)

    expect(result0.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/detail/123',
        method: 'POST',
        params: {
          id: 123,
        },
        query: {
          a: 1,
          b: '1',
          c: false,
        },
        body: null,
        headers: {
          a: 1,
          b: '1',
          c: false,
        },
        cookies: {
          a: 1,
          b: '1',
          c: false,
        },
      },
    })

    const request1 = {
      ...request0,
      body: {
        a: 1,
        b: '1',
        c: false,
        d: {
          a: 1,
          b: '1',
          c: false,
        },
      },
    }

    const result1 = await router.run(request1)

    expect(result1.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/detail/123',
        method: 'POST',
        params: {
          id: 123,
        },
        query: {
          a: 1,
          b: '1',
          c: false,
        },
        body: {
          a: 1,
          b: '1',
          c: false,
          d: {
            a: 1,
            b: '1',
            c: false,
          },
        },
        headers: {
          a: 1,
          b: '1',
          c: false,
        },
        cookies: {
          a: 1,
          b: '1',
          c: false,
        },
      },
    })

    const request2 = {
      ...request1,
      query: {},
    }

    expect(() => router.run(request2)).toThrow()
  })

  it('can validate number | int | float | boolean strictly', async () => {
    const router0 = Router()

    const router1 = Router()

    router0
      .match({
        pathname: '/',
        query: {
          id: Strict(Number),
        },
      })
      .use((request) => {
        return Response.json(request)
      })

    router1
      .match({
        pathname: '/',
        query: {
          id: Number,
        },
      })
      .use((request) => {
        return Response.json(request)
      })

    expect(() =>
      router0.run({
        pathname: '/',
        query: {
          id: '123',
        },
      }),
    ).toThrow()

    const result1 = await router1.run({
      pathname: '/',
      query: {
        id: '123',
      },
    })

    expect(result1.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/',
        query: {
          id: 123,
        },
      },
    })
  })

  it('support passing regexp to schema.pathname', async () => {
    const router = Router()

    router
      .match({
        pathname: /^\/test/i,
      })
      .use((request) => {
        return Response.json(request)
      })

    expect(() =>
      router.run({
        pathname: '/abc',
      }),
    ).toThrow()

    expect(() =>
      router.run({
        pathname: '/abc/test',
      }),
    ).toThrow()

    const result0 = await router.run({
      pathname: '/test/abc',
    })

    const result1 = await router.run({
      pathname: '/test/efg',
    })

    expect(result0.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/test/abc',
      },
    })

    expect(result1.info.body).toEqual({
      type: 'json',
      value: {
        pathname: '/test/efg',
      },
    })
  })

  it('support detect response body and content-type', async () => {
    const router = Router()

    const contentTypes = [] as string[]

    const bodyTypes = [] as string[]

    router.use(async (request, next) => {
      const response = await next(request)

      if (response.is('json')) {
        contentTypes.push('json')
      }

      if (response.is('text')) {
        contentTypes.push('text')
      }

      if (response.is('html')) {
        contentTypes.push('html')
      }

      if (response.info.body?.type) {
        bodyTypes.push(response.info.body?.type)
      }

      return response
    })

    router
      .match({
        pathname: '/:type',
        params: {
          type: Union(
            Literal('json'),
            Literal('text'),
            Literal('html'),
            Literal('string'),
            Literal('buffer'),
            Literal('stream'),
          ),
        },
      })
      .use((request) => {
        if (request.params.type === 'string') {
          return Response.string('test')
        }

        if (request.params.type === 'text') {
          return Response.text('test')
        }

        if (request.params.type === 'buffer') {
          return Response.buffer(Buffer.from('test'))
        }

        if (request.params.type === 'html') {
          return Response.html('test')
        }

        if (request.params.type === 'json') {
          return Response.json('test')
        }

        if (request.params.type === 'stream') {
          return Response.stream(
            new Stream.Readable({
              read() {
                this.push('test')
                this.push(null)
              },
            }),
          )
        }

        return Response.json(request)
      })

    await router.run({
      pathname: '/json',
    })

    await router.run({
      pathname: '/json',
    })

    await router.run({
      pathname: '/text',
    })

    await router.run({
      pathname: '/buffer',
    })

    await router.run({
      pathname: '/html',
    })

    await router.run({
      pathname: '/stream',
    })

    await router.run({
      pathname: '/string',
    })

    expect(contentTypes).toEqual(['json', 'json', 'text', 'html'])
    expect(bodyTypes).toEqual(['json', 'json', 'string', 'buffer', 'string', 'stream', 'string'])
  })
})
Example #25
Source File: http.test.ts    From farrow with MIT License 4 votes vote down vote up
describe('Http', () => {
  describe('Response', () => {
    it('support text response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.text(JSON.stringify(data))
        })

      await request(http.server())
        .get('/test')
        .expect('Content-Type', /text/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support html response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.html(JSON.stringify(data))
        })

      await request(http.server())
        .get('/test')
        .expect('Content-Type', /html/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support json response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.json(data)
        })

      await request(http.server())
        .get('/test')
        .set('Accept', 'application/json')
        .expect('Content-Type', /json/)
        .expect(200, {
          pathname: '/test',
        })
    })

    it('support empty response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.empty()
        })

      await request(http.server()).get('/test').expect(204)
    })

    it('support redirecting', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.redirect('/redirected')
        })

      await request(http.server()).get('/test').expect('Location', '/redirected').expect(302)
    })

    it('support stream response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          const stream = new Stream.Readable({
            read() {
              this.push('test stream')
              this.push(null)
            },
          })
          return Response.stream(stream)
        })

      await request(http.server()).get('/test').expect(200, 'test stream')
    })

    it('support buffer response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.buffer(Buffer.from('test buffer'))
        })

      await request(http.server()).get('/test').expect(200, 'test buffer')
    })

    it('support file response', async () => {
      const http = createHttp()

      const filename = path.join(__dirname, 'http.test.ts')

      const content = await fs.promises.readFile(filename)

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.file(filename)
        })

      await request(http.server()).get('/test').expect(200, content)
    })

    it('support string response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test string body')
        })

      await request(http.server()).get('/test').expect(200, 'test string body')
    })

    it('support custom response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.custom(({ res, requestInfo, responseInfo }) => {
            res.end(
              JSON.stringify({
                requestInfo: {
                  ...requestInfo,
                  headers: null,
                },
                responseInfo,
              }),
            )
          })
        })

      await request(http.server())
        .get('/test')
        .expect(
          200,
          JSON.stringify({
            requestInfo: {
              pathname: '/test',
              method: 'GET',
              query: {},
              body: null,
              headers: null,
              cookies: {},
            },
            responseInfo: {},
          }),
        )
    })

    it('should not be matched when pathname or method failed to match', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test',
          method: 'POST',
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server).get('/test').expect(404)
      await request(server).post('/test0').expect(404)
      await request(server).post('/test').expect(200, {
        pathname: '/test',
        method: 'POST',
      })
    })

    it('should respond 500 in block mode when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: true,
        },
      )

      await request(http.server()).get('/test').expect(500)
    })

    it('should not respond 404 if matchOptions.bloack = false when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: false,
        },
      )

      await request(http.server()).get('/test').expect(404)
    })

    it('should responding 500 when middleware throwed error', async () => {
      const http = createHttp()

      http.use((request, next) => {
        if (request.pathname === '/sync') {
          throw new Error('sync error')
        }
        return next()
      })

      http.use(async (request, next) => {
        if (request.pathname === '/async') {
          await delay(10)
          throw new Error('async error')
        }
        return next()
      })

      http.use(() => {
        return Response.json({
          ok: true,
        })
      })

      await request(http.server()).get('/sync').expect(500)
      await request(http.server()).get('/async').expect(500)
      await request(http.server()).get('/').expect(200, {
        ok: true,
      })
    })

    it('support set response status', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-status',
        })
        .use(() => {
          return Response.status(302, 'abc').text('ok')
        })

      await request(server).get('/test-status').expect(302)
    })

    it('support set response headers', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-header',
        })
        .use(() => {
          return Response.header('test-header', 'header-value').text('ok')
        })

      http
        .match({
          pathname: '/test-headers',
        })
        .use(() => {
          return Response.headers({
            'test-header1': 'header1-value',
            'test-header2': 'header2-value',
          }).text('ok')
        })

      await request(server).get('/test-header').expect('test-header', 'header-value').expect(200)
      await request(server)
        .get('/test-headers')
        .expect('test-header1', 'header1-value')
        .expect('test-header2', 'header2-value')
        .expect(200)
    })

    it('support set response cookies', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-cookie',
          method: 'POST',
        })
        .use(() => {
          return Response.cookie('test-cookie', 'cookie-value').text('set cookie ok')
        })

      http
        .match({
          pathname: '/test-cookies',
          method: 'POST',
        })
        .use(() => {
          return Response.cookies({
            'test-cookie1': 'cookie1-value',
            'test-cookie2': 'cookie2-value',
          }).text('set cookies ok')
        })

      await request(server)
        .post('/test-cookie')
        .expect('set-cookie', 'test-cookie=cookie-value; path=/; httponly')
        .expect(200, 'set cookie ok')

      await request(server)
        .post('/test-cookies')
        .expect(
          'set-cookie',
          ['test-cookie1=cookie1-value; path=/; httponly', 'test-cookie2=cookie2-value; path=/; httponly'].join(','),
        )
        .expect(200, 'set cookies ok')
    })

    it('support set response vary', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.vary('origin', 'cookie').text('ok')
        })

      await request(server).get('/test-vary').expect('vary', 'origin, cookie').expect(200, 'ok')
    })

    it('support set attachment', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.attachment('test.txt').text('ok')
        })

      await request(server)
        .get('/test-vary')
        .expect('content-disposition', 'attachment; filename="test.txt"')
        .expect(200, 'ok')
    })

    it('support set content-type via mime-type', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-type',
          method: 'POST',
          body: {
            type: String,
          },
        })
        .use((request) => {
          return Response.type(request.body.type).buffer(
            Buffer.from(
              JSON.stringify({
                success: true,
                data: request.body,
              }),
            ),
          )
        })

      await request(server)
        .post('/test-type')
        .send({
          type: 'png',
        })
        .expect('Content-Type', 'image/png')

      await request(server)
        .post('/test-type')
        .send({
          type: 'jpg',
        })
        .expect('Content-Type', 'image/jpeg')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.js',
        })
        .expect('Content-Type', 'application/javascript; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.html',
        })
        .expect('Content-Type', 'text/html; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.css',
        })
        .expect('Content-Type', 'text/css; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.json',
        })
        .expect('Content-Type', 'application/json; charset=utf-8')
    })

    it('support merging response in two directions', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test-merge-01',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return response.merge(Response.text('one'))
        })
        .use(() => {
          return Response.text('two')
        })

      http
        .match({
          pathname: '/test-merge-02',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return Response.text('one').merge(response)
        })
        .use(() => {
          return Response.text('two')
        })

      await request(server).get('/test-merge-01').expect(200, 'one')
      await request(server).get('/test-merge-02').expect(200, 'two')
      await request(server).get('/test-merge-03').expect(404)
    })

    it('support serving static files', async () => {
      const http = createHttp()
      const server = http.server()

      const dirname = path.join(__dirname, '../')

      const read = async (filename: string) => {
        const buffer = await fs.promises.readFile(path.join(dirname, filename))
        return buffer.toString()
      }

      http.serve('/static', dirname)

      await request(server)
        .get('/static/package.json')
        .expect('Content-Type', /json/)
        .expect(200, await read('package.json'))

      await request(server)
        .get('/static/README.md')
        .expect('Content-Type', /markdown/)
        .expect(200, await read('README.md'))

      await request(server)
        .get('/static/dist/index.js')
        .expect(200, await read('dist/index.js'))

      await request(server).get('/static/abc').expect(404)
    })

    it('should go through when the file does not exist in router.serve', async () => {
      const http = createHttp()
      const server = http.server()
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      http.use(() => {
        return Response.text('Cheer!')
      })

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/cheer').expect(200, 'Cheer!')

      await request(server).get('/static').expect(200, 'Cheer!')
    })

    it('should only serve files under argument.dirname', async () => {
      const http = createHttp()
      const server = http.server()
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/../bar.js').expect(404, '404 Not Found')
    })

    it('support capturing response by type', async () => {
      const http = createHttp()
      const server = http.server()

      http.capture('string', (stringBody) => {
        return Response.text(`capture: ${stringBody.value}`)
      })

      http.capture('json', (jsonBody) => {
        return Response.json({
          capture: true,
          original: jsonBody.value,
        })
      })

      http
        .match({
          pathname: '/test-text',
        })
        .use(() => {
          return Response.text('some text')
        })

      http
        .match({
          pathname: '/test-json',
        })
        .use(() => {
          return Response.json({
            data: 'ok',
          })
        })

      await request(server).get('/test-text').expect('Content-Type', /text/).expect(200, 'capture: some text')

      await request(server)
        .get('/test-json')
        .expect('Content-Type', /json/)
        .expect(200, {
          capture: true,
          original: {
            data: 'ok',
          },
        })
    })

    it('support injecting context', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = http.server()

      http.use(() => {
        const ctx = TestContext.use()
        const { value } = ctx
        ctx.value += 1
        return Response.text(value.toString())
      })

      await request(server).get('/').expect(200, '10')
      await request(server).get('/any').expect(200, '10')
    })

    it('support async hooks', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = http.server()

      http.use(async (request, next) => {
        await delay(1)
        const response = await next(request)
        expect(TestContext.get()).toBe(11)
        return response
      })

      http.use(() => {
        TestContext.set(TestContext.get() + 1)
        return Response.text(`${TestContext.get()}`)
      })

      await request(server).get('/').expect(200, '11')
      await request(server).get('/any').expect(200, '11')
    })

    it('should only handle GET method by default', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(404)
      await request(server).post('/test').expect(404)
      await request(server).delete('/test').expect(404)
      await request(server).put('/test').expect(404)
    })

    it('support match multiple methods', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .match({
          pathname: '/test',
          method: ['get', 'options', 'post', 'delete', 'put'],
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(200, 'test')
      await request(server).post('/test').expect(200, 'test')
      await request(server).delete('/test').expect(200, 'test')
      await request(server).put('/test').expect(200, 'test')
    })

    it('should respond 400 if request schema was not matched', async () => {
      const http = createHttp()
      const server = http.server()

      http.get('/<name:string>/<age:int>').use((request) => {
        return Response.json({
          name: request.params.name,
          age: request.params.age,
        })
      })

      await request(server).get('/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/farrow/abc').expect(400)
    })

    it('should handle schema-error if options.onSchemaError is used', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .get(
          '/catch0/<name:string>/<age:int>',
          {},
          {
            onSchemaError: (error) => {
              return Response.json({
                error,
              })
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      let count = 0

      http
        .get(
          '/catch1/<name:string>/<age:int>',
          {},
          {
            onSchemaError: () => {
              count = 1
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      await request(server).get('/catch0/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server)
        .get('/catch0/farrow/abc')
        .expect(200, {
          error: {
            path: ['params', 'age'],
            message: 'abc is not an integer',
          },
        })

      await request(server).get('/catch1/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/catch1/farrow/abc').expect(400)

      expect(count).toBe(1)
    })
  })

  describe('Request', () => {
    it('support access native req/res via useReq/useRes', async () => {
      const http = createHttp()

      http.use(() => {
        const req = useReq()
        const res = useRes()

        res.statusCode = 200
        res.end(req.url)

        return Response.custom()
      })

      await request(http.server()).get('/test-abc').expect(200, '/test-abc')
    })

    it('support accessing request info via useRequestInfo', async () => {
      const http = createHttp()

      http.use(() => {
        const info = useRequestInfo()

        return Response.json({
          ...info,
          headers: {
            cookie: info.headers?.cookie,
            'content-type': 'application/json',
            accept: 'application/json',
            'content-length': '13',
          },
        })
      })

      await request(http.server())
        .post('/test-abc?a=1&b=2')
        .set('Accept', 'application/json')
        .set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])
        .send({
          a: 1,
          b: 2,
        })
        .expect(200, {
          pathname: '/test-abc',
          method: 'POST',
          query: { a: '1', b: '2' },
          body: { a: 1, b: 2 },
          headers: {
            accept: 'application/json',
            cookie: 'nameOne=valueOne;nameTwo=valueTwo',
            'content-type': 'application/json',
            'content-length': '13',
          },
          cookies: { nameOne: 'valueOne', nameTwo: 'valueTwo' },
        })
    })

    it('support passing new request info to downstream', async () => {
      const http = createHttp()
      const server = http.server()

      http.use((request, next) => {
        if (request.pathname.startsWith('/new')) {
          return next({
            ...request,
            pathname: request.pathname.replace('/new', ''),
            query: {
              ...request.query,
              new: true,
            },
          })
        }
        return next()
      })

      http
        .match({
          pathname: '/test',
          query: {
            a: String,
            new: Nullable(Boolean),
          },
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server)
        .get('/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
          },
        })

      await request(server)
        .get('/new/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
            new: true,
          },
        })
    })
  })

  describe('Router', () => {
    it('should support add router', async () => {
      const http = createHttp()
      const router = Router()
      const server = http.server()

      router
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          return Response.text(request.pathname)
        })

      http.use(router)
      http.route('/base').use(router)

      await request(server).get('/abc').expect(200, '/abc')
      await request(server).get('/base/abc').expect(200, '/abc')
    })

    it('should support using router in another router', async () => {
      const http = createHttp()
      const router0 = Router()
      const router1 = Router()
      const router2 = Router()
      const server = http.server()

      http.use(async (request, next) => {
        const basenames = useBasenames()
        const before = basenames.value
        const response = await next(request)
        const after = basenames.value

        // should reset basenames after next(...)
        expect(before).toEqual(after)
        return response
      })

      http.route('/router0').use(router0)
      router0.route('/router1').use(router1)
      http.route('/router2').use(router2)

      http
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'http',
            prefix,
            pathname: request.pathname,
          })
        })

      router0
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router0',
            prefix,
            pathname: request.pathname,
          })
        })

      router1
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router1',
            prefix,
            pathname: request.pathname,
          })
        })

      router2.use((request) => {
        const prefix = usePrefix()
        return Response.json({
          from: 'router2',
          prefix,
          pathname: request.pathname,
        })
      })

      await request(server).get('/abc').expect(200, {
        from: 'http',
        prefix: '',
        pathname: '/abc',
      })

      await request(server).get('/router0/abc').expect(200, {
        from: 'router0',
        prefix: '/router0',
        pathname: '/abc',
      })

      await request(server).get('/router0/router1/abc').expect(200, {
        from: 'router1',
        prefix: '/router0/router1',
        pathname: '/abc',
      })

      await request(server).get('/router2').expect(200, {
        from: 'router2',
        prefix: '/router2',
        pathname: '/',
      })
    })

    it('support setting custom content-type for Response.file', async () => {
      const http = createHttp()
      const server = http.server()
      const filename = path.join(__dirname, './fixtures/static/foo.js')
      const content = await fs.promises.readFile(filename)

      http.get('/raw').use(() => {
        return Response.file(filename)
      })

      http.get('/text').use(() => {
        return Response.type('text').file(filename)
      })

      await request(server)
        .get('/raw')
        .expect('Content-Type', /javascript/)
        .expect(200, content.toString())

      await request(server).get('/text').expect('Content-Type', /text/).expect(200, content.toString())
    })

    it('support remove cookies or headers', async () => {
      const http = createHttp()
      const server = http.server()

      http
        .get(
          '/test',
          {},
          {
            block: false,
          },
        )
        .use(async (request, next) => {
          const response = await next(request)

          return response
            .cookies({
              a: '',
              b: '',
            })
            .headers({
              c: '',
              d: '',
            })
        })

      http.get('/test').use(() => {
        return Response.cookies({
          a: '1',
          b: '2',
          c: '3',
          d: '4',
        })
          .headers({
            a: '1',
            b: '2',
            c: '3',
            d: '4',
          })
          .text('OK')
      })

      await request(server)
        .get('/test')
        .expect((res) => {
          expect(res.headers['set-cookie'].length).toEqual(4)
          expect(res.headers['a']).toBe('1')
          expect(res.headers['b']).toBe('2')
        })
        .expect(200, 'OK')
    })
  })
})
Example #26
Source File: express.test.ts    From farrow with MIT License 4 votes vote down vote up
describe('Express', () => {
  describe('Response', () => {
    it('support text response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.text(JSON.stringify(data))
        })

      await request(createApp(http))
        .get('/test')
        .expect('Content-Type', /text/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support html response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.html(JSON.stringify(data))
        })

      await request(createApp(http))
        .get('/test')
        .expect('Content-Type', /html/)
        .expect(
          'Content-Length',
          JSON.stringify({
            pathname: '/test',
          }).length.toString(),
        )
        .expect(
          200,
          JSON.stringify({
            pathname: '/test',
          }),
        )
    })

    it('support json response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use((data) => {
          return Response.json(data)
        })

      await request(createApp(http))
        .get('/test')
        .set('Accept', 'application/json')
        .expect('Content-Type', /json/)
        .expect(200, {
          pathname: '/test',
        })
    })

    it('support empty response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.empty()
        })

      await request(createApp(http)).get('/test').expect(204)
    })

    it('support redirecting', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.redirect('/redirected')
        })

      await request(createApp(http)).get('/test').expect('Location', '/redirected').expect(302)
    })

    it('support stream response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          const stream = new Stream.Readable({
            read() {
              this.push('test stream')
              this.push(null)
            },
          })
          return Response.stream(stream)
        })

      await request(createApp(http)).get('/test').expect(200, 'test stream')
    })

    it('support buffer response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.buffer(Buffer.from('test buffer'))
        })

      await request(createApp(http)).get('/test').expect(200, 'test buffer')
    })

    it('support file response', async () => {
      const http = createHttp()

      const filename = path.join(__dirname, 'express.test.ts')

      const content = await fs.promises.readFile(filename)

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.file(filename)
        })

      await request(createApp(http)).get('/test').expect(200, content)
    })

    it('support string response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test string body')
        })

      await request(createApp(http)).get('/test').expect(200, 'test string body')
    })

    it('support custom response', async () => {
      const http = createHttp()

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.custom(({ res, requestInfo, responseInfo }) => {
            res.end(
              JSON.stringify({
                requestInfo: {
                  ...requestInfo,
                  headers: null,
                },
                responseInfo,
              }),
            )
          })
        })

      await request(createApp(http))
        .get('/test')
        .expect(
          200,
          JSON.stringify({
            requestInfo: {
              pathname: '/test',
              method: 'GET',
              query: {},
              body: null,
              headers: null,
              cookies: {},
            },
            responseInfo: {},
          }),
        )
    })

    it('should not be matched when pathname or method failed to match', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test',
          method: 'POST',
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server).get('/test').expect(404)
      await request(server).post('/test0').expect(404)
      await request(server).post('/test').expect(200, {
        pathname: '/test',
        method: 'POST',
      })
    })

    it('should respond 500 in block mode when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: true,
        },
      )

      await request(createApp(http)).get('/test').expect(500)
    })

    it('should not respond 404 if matchOptions.bloack = false when there are no middlewares handling request', async () => {
      const http = createHttp()

      http.match(
        {
          pathname: '/test',
        },
        {
          block: false,
        },
      )

      await request(createApp(http)).get('/test').expect(404)
    })

    it('should responding 500 when middleware throwed error', async () => {
      const http = createHttp()

      http.use((request, next) => {
        if (request.pathname === '/sync') {
          throw new Error('sync error')
        }
        return next()
      })

      http.use(async (request, next) => {
        if (request.pathname === '/async') {
          await delay(10)
          throw new Error('async error')
        }
        return next()
      })

      http.use(() => {
        return Response.json({
          ok: true,
        })
      })

      await request(createApp(http)).get('/sync').expect(500)
      await request(createApp(http)).get('/async').expect(500)
      await request(createApp(http)).get('/').expect(200, {
        ok: true,
      })
    })

    it('support set response status', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-status',
        })
        .use(() => {
          return Response.status(302, 'abc').text('ok')
        })

      await request(server).get('/test-status').expect(302)
    })

    it('support set response headers', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-header',
        })
        .use(() => {
          return Response.header('test-header', 'header-value').text('ok')
        })

      http
        .match({
          pathname: '/test-headers',
        })
        .use(() => {
          return Response.headers({
            'test-header1': 'header1-value',
            'test-header2': 'header2-value',
          }).text('ok')
        })

      await request(server).get('/test-header').expect('test-header', 'header-value').expect(200)
      await request(server)
        .get('/test-headers')
        .expect('test-header1', 'header1-value')
        .expect('test-header2', 'header2-value')
        .expect(200)
    })

    it('support set response cookies', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-cookie',
          method: 'POST',
        })
        .use(() => {
          return Response.cookie('test-cookie', 'cookie-value').text('set cookie ok')
        })

      http
        .match({
          pathname: '/test-cookies',
          method: 'POST',
        })
        .use(() => {
          return Response.cookies({
            'test-cookie1': 'cookie1-value',
            'test-cookie2': 'cookie2-value',
          }).text('set cookies ok')
        })

      await request(server)
        .post('/test-cookie')
        .expect('set-cookie', 'test-cookie=cookie-value; path=/; httponly')
        .expect(200, 'set cookie ok')

      await request(server)
        .post('/test-cookies')
        .expect(
          'set-cookie',
          ['test-cookie1=cookie1-value; path=/; httponly', 'test-cookie2=cookie2-value; path=/; httponly'].join(','),
        )
        .expect(200, 'set cookies ok')
    })

    it('support set response vary', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.vary('origin', 'cookie').text('ok')
        })

      await request(server).get('/test-vary').expect('vary', 'origin, cookie').expect(200, 'ok')
    })

    it('support set attachment', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-vary',
        })
        .use(() => {
          return Response.attachment('test.txt').text('ok')
        })

      await request(server)
        .get('/test-vary')
        .expect('content-disposition', 'attachment; filename="test.txt"')
        .expect(200, 'ok')
    })

    it('support set content-type via mime-type', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-type',
          method: 'POST',
          body: {
            type: String,
          },
        })
        .use((request) => {
          return Response.type(request.body.type).buffer(
            Buffer.from(
              JSON.stringify({
                success: true,
                data: request.body,
              }),
            ),
          )
        })

      await request(server)
        .post('/test-type')
        .send({
          type: 'png',
        })
        .expect('Content-Type', 'image/png')

      await request(server)
        .post('/test-type')
        .send({
          type: 'jpg',
        })
        .expect('Content-Type', 'image/jpeg')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.js',
        })
        .expect('Content-Type', 'application/javascript; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.html',
        })
        .expect('Content-Type', 'text/html; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.css',
        })
        .expect('Content-Type', 'text/css; charset=utf-8')

      await request(server)
        .post('/test-type')
        .send({
          type: 'file.json',
        })
        .expect('Content-Type', 'application/json; charset=utf-8')
    })

    it('support merging response in two directions', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test-merge-01',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return response.merge(Response.text('one'))
        })
        .use(() => {
          return Response.text('two')
        })

      http
        .match({
          pathname: '/test-merge-02',
        })
        .use(async (request, next) => {
          const response = await next(request)
          return Response.text('one').merge(response)
        })
        .use(() => {
          return Response.text('two')
        })

      await request(server).get('/test-merge-01').expect(200, 'one')
      await request(server).get('/test-merge-02').expect(200, 'two')
      await request(server).get('/test-merge-03').expect(404)
    })

    it('support serving static files', async () => {
      const http = createHttp()
      const server = createApp(http)

      const dirname = path.join(__dirname, '../')

      const read = async (filename: string) => {
        const buffer = await fs.promises.readFile(path.join(dirname, filename))
        return buffer.toString()
      }

      http.serve('/static', dirname)

      await request(server)
        .get('/static/package.json')
        .expect('Content-Type', /json/)
        .expect(200, await read('package.json'))

      await request(server)
        .get('/static/README.md')
        .expect('Content-Type', /markdown/)
        .expect(200, await read('README.md'))

      await request(server)
        .get('/static/tsconfig.json')
        .expect(200, await read('tsconfig.json'))

      await request(server).get('/static/abc').expect(404)
    })

    it('should go through when the file does not exist in router.serve', async () => {
      const http = createHttp()
      const server = createApp(http)
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      http.use(() => {
        return Response.text('Cheer!')
      })

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/cheer').expect(200, 'Cheer!')

      await request(server).get('/static').expect(200, 'Cheer!')
    })

    it('should only serve files under argument.dirname', async () => {
      const http = createHttp()
      const server = createApp(http)
      const dirname = path.join(__dirname, './fixtures/static')
      const content = await fs.promises.readFile(path.join(dirname, 'foo.js'))

      http.serve('/static', dirname)

      await request(server).get('/static/foo.js').expect(200, content.toString())

      await request(server).get('/static/../bar.js').expect(404)
    })

    it('support capturing response by type', async () => {
      const http = createHttp()
      const server = createApp(http)

      http.capture('string', (stringBody) => {
        return Response.text(`capture: ${stringBody.value}`)
      })

      http.capture('json', (jsonBody) => {
        return Response.json({
          capture: true,
          original: jsonBody.value,
        })
      })

      http
        .match({
          pathname: '/test-text',
        })
        .use(() => {
          return Response.text('some text')
        })

      http
        .match({
          pathname: '/test-json',
        })
        .use(() => {
          return Response.json({
            data: 'ok',
          })
        })

      await request(server).get('/test-text').expect('Content-Type', /text/).expect(200, 'capture: some text')

      await request(server)
        .get('/test-json')
        .expect('Content-Type', /json/)
        .expect(200, {
          capture: true,
          original: {
            data: 'ok',
          },
        })
    })

    it('support injecting context', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = createApp(http)

      http.use(() => {
        const ctx = TestContext.use()
        const { value } = ctx
        ctx.value += 1
        return Response.text(value.toString())
      })

      await request(server).get('/').expect(200, '10')
      await request(server).get('/any').expect(200, '10')
    })

    it('support async hooks', async () => {
      const TestContext = createContext(0)

      const http = createHttp({
        contexts: () => {
          return {
            test: TestContext.create(10),
          }
        },
      })

      const server = createApp(http)

      http.use(async (request, next) => {
        await delay(1)
        const response = await next(request)
        expect(TestContext.get()).toBe(11)
        return response
      })

      http.use(() => {
        TestContext.set(TestContext.get() + 1)
        return Response.text(`${TestContext.get()}`)
      })

      await request(server).get('/').expect(200, '11')
      await request(server).get('/any').expect(200, '11')
    })

    it('should only handle GET method by default', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test',
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(404)
      await request(server).post('/test').expect(404)
      await request(server).delete('/test').expect(404)
      await request(server).put('/test').expect(404)
    })

    it('support match multiple methods', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .match({
          pathname: '/test',
          method: ['get', 'options', 'post', 'delete', 'put'],
        })
        .use(() => {
          return Response.text('test')
        })

      await request(server).get('/test').expect(200, 'test')
      await request(server).options('/test').expect(200, 'test')
      await request(server).post('/test').expect(200, 'test')
      await request(server).delete('/test').expect(200, 'test')
      await request(server).put('/test').expect(200, 'test')
    })

    it('should respond 400 if request schema was not matched', async () => {
      const http = createHttp()
      const server = createApp(http)

      http.get('/<name:string>/<age:int>').use((request) => {
        return Response.json({
          name: request.params.name,
          age: request.params.age,
        })
      })

      await request(server).get('/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/farrow/abc').expect(400)
    })

    it('should handle schema-error if options.onSchemaError is used', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .get(
          '/catch0/<name:string>/<age:int>',
          {},
          {
            onSchemaError: (error) => {
              return Response.json({
                error,
              })
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      let count = 0

      http
        .get(
          '/catch1/<name:string>/<age:int>',
          {},
          {
            onSchemaError: () => {
              count = 1
            },
          },
        )
        .use((request) => {
          return Response.json({
            name: request.params.name,
            age: request.params.age,
          })
        })

      await request(server).get('/catch0/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server)
        .get('/catch0/farrow/abc')
        .expect(200, {
          error: {
            path: ['params', 'age'],
            message: 'abc is not an integer',
          },
        })

      await request(server).get('/catch1/farrow/20').expect(200, {
        name: 'farrow',
        age: 20,
      })

      await request(server).get('/catch1/farrow/abc').expect(400)

      expect(count).toBe(1)
    })
  })

  describe('Request', () => {
    it('support access native req/res via useReq/useRes', async () => {
      const http = createHttp()

      http.use(() => {
        const req = useReq()
        const res = useRes()

        res.statusCode = 200
        res.end(req.url)

        return Response.custom()
      })

      await request(createApp(http)).get('/test-abc').expect(200, '/test-abc')
    })

    it('support accessing request info via useRequestInfo', async () => {
      const http = createHttp()

      http.use(() => {
        const info = useRequestInfo()

        return Response.json({
          ...info,
          headers: {
            cookie: info.headers?.cookie,
            'content-type': 'application/json',
            accept: 'application/json',
            'content-length': '13',
          },
        })
      })

      await request(createApp(http))
        .post('/test-abc?a=1&b=2')
        .set('Accept', 'application/json')
        .set('Cookie', ['nameOne=valueOne;nameTwo=valueTwo'])
        .send({
          a: 1,
          b: 2,
        })
        .expect(200, {
          pathname: '/test-abc',
          method: 'POST',
          query: { a: '1', b: '2' },
          body: { a: 1, b: 2 },
          headers: {
            accept: 'application/json',
            cookie: 'nameOne=valueOne;nameTwo=valueTwo',
            'content-type': 'application/json',
            'content-length': '13',
          },
          cookies: { nameOne: 'valueOne', nameTwo: 'valueTwo' },
        })
    })

    it('support passing new request info to downstream', async () => {
      const http = createHttp()
      const server = createApp(http)

      http.use((request, next) => {
        if (request.pathname.startsWith('/new')) {
          return next({
            ...request,
            pathname: request.pathname.replace('/new', ''),
            query: {
              ...request.query,
              new: true,
            },
          })
        }
        return next()
      })

      http
        .match({
          pathname: '/test',
          query: {
            a: String,
            new: Nullable(Boolean),
          },
        })
        .use((request) => {
          return Response.json(request)
        })

      await request(server)
        .get('/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
          },
        })

      await request(server)
        .get('/new/test?a=1')
        .expect(200, {
          pathname: '/test',
          query: {
            a: '1',
            new: true,
          },
        })
    })
  })

  describe('Router', () => {
    it('should support add router', async () => {
      const http = createHttp()
      const router = Router()
      const server = createApp(http)

      router
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          return Response.text(request.pathname)
        })

      http.use(router)
      http.route('/base').use(router)

      await request(server).get('/abc').expect(200, '/abc')
      await request(server).get('/base/abc').expect(200, '/abc')
    })

    it('should support using router in another router', async () => {
      const http = createHttp()
      const router0 = Router()
      const router1 = Router()
      const router2 = Router()
      const server = createApp(http)

      http.use(async (request, next) => {
        const basenames = useBasenames()
        const before = basenames.value
        const response = await next(request)
        const after = basenames.value

        // should reset basenames after next(...)
        expect(before).toEqual(after)
        return response
      })

      http.route('/router0').use(router0)
      router0.route('/router1').use(router1)
      http.route('/router2').use(router2)

      http
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'http',
            prefix,
            pathname: request.pathname,
          })
        })

      router0
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router0',
            prefix,
            pathname: request.pathname,
          })
        })

      router1
        .match({
          pathname: '/abc',
        })
        .use((request) => {
          const prefix = usePrefix()
          return Response.json({
            from: 'router1',
            prefix,
            pathname: request.pathname,
          })
        })

      router2.use((request) => {
        const prefix = usePrefix()
        return Response.json({
          from: 'router2',
          prefix,
          pathname: request.pathname,
        })
      })

      await request(server).get('/abc').expect(200, {
        from: 'http',
        prefix: '',
        pathname: '/abc',
      })

      await request(server).get('/router0/abc').expect(200, {
        from: 'router0',
        prefix: '/router0',
        pathname: '/abc',
      })

      await request(server).get('/router0/router1/abc').expect(200, {
        from: 'router1',
        prefix: '/router0/router1',
        pathname: '/abc',
      })

      await request(server).get('/router2').expect(200, {
        from: 'router2',
        prefix: '/router2',
        pathname: '/',
      })
    })

    it('support setting custom content-type for Response.file', async () => {
      const http = createHttp()
      const server = createApp(http)
      const filename = path.join(__dirname, './fixtures/static/foo.js')
      const content = await fs.promises.readFile(filename)

      http.get('/raw').use(() => {
        return Response.file(filename)
      })

      http.get('/text').use(() => {
        return Response.type('text').file(filename)
      })

      await request(server)
        .get('/raw')
        .expect('Content-Type', /javascript/)
        .expect(200, content.toString())

      await request(server).get('/text').expect('Content-Type', /text/).expect(200, content.toString())
    })

    it('support remove cookies or headers', async () => {
      const http = createHttp()
      const server = createApp(http)

      http
        .get(
          '/test',
          {},
          {
            block: false,
          },
        )
        .use(async (request, next) => {
          const response = await next(request)

          return response
            .cookies({
              a: '',
              b: '',
            })
            .headers({
              c: '',
              d: '',
            })
        })

      http.get('/test').use(() => {
        return Response.cookies({
          a: '1',
          b: '2',
          c: '3',
          d: '4',
        })
          .headers({
            a: '1',
            b: '2',
            c: '3',
            d: '4',
          })
          .text('OK')
      })

      await request(server)
        .get('/test')
        .expect((res) => {
          expect(res.headers['set-cookie'].length).toEqual(4)
          expect(res.headers['a']).toBe('1')
          expect(res.headers['b']).toBe('2')
        })
        .expect(200, 'OK')
    })
  })
})
Example #27
Source File: openStackSwift.test.ts    From backstage with Apache License 2.0 4 votes vote down vote up
jest.mock('@trendyol-js/openstack-swift-sdk', () => {
  const {
    ContainerMetaResponse,
    DownloadResponse,
    NotFound,
    ObjectMetaResponse,
    UploadResponse,
  }: typeof import('@trendyol-js/openstack-swift-sdk') = jest.requireActual(
    '@trendyol-js/openstack-swift-sdk',
  );

  const checkFileExists = async (Key: string): Promise<boolean> => {
    // Key will always have / as file separator irrespective of OS since cloud providers expects /.
    // Normalize Key to OS specific path before checking if file exists.
    const filePath = path.join(storageRootDir, Key);

    try {
      await fs.access(filePath, fs.constants.F_OK);
      return true;
    } catch (err) {
      return false;
    }
  };

  const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
    return new Promise((resolve, reject) => {
      try {
        const chunks: any[] = [];
        stream.on('data', chunk => chunks.push(chunk));
        stream.on('error', reject);
        stream.on('end', () => resolve(Buffer.concat(chunks)));
      } catch (e) {
        throw new Error(`Unable to parse the response data ${e.message}`);
      }
    });
  };

  return {
    __esModule: true,
    SwiftClient: class {
      async getMetadata(_containerName: string, file: string) {
        const fileExists = await checkFileExists(file);
        if (fileExists) {
          return new ObjectMetaResponse({
            fullPath: file,
          });
        }
        return new NotFound();
      }

      async getContainerMetadata(containerName: string) {
        if (containerName === 'mock') {
          return new ContainerMetaResponse({
            size: 10,
          });
        }
        return new NotFound();
      }

      async upload(
        _containerName: string,
        destination: string,
        stream: Readable,
      ) {
        try {
          const filePath = path.join(storageRootDir, destination);
          const fileBuffer = await streamToBuffer(stream);

          await fs.writeFile(filePath, fileBuffer);
          const fileExists = await checkFileExists(destination);

          if (fileExists) {
            return new UploadResponse(filePath);
          }
          const errorMessage = `Unable to upload file(s) to OpenStack Swift.`;
          throw new Error(errorMessage);
        } catch (error) {
          const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${error}`;
          throw new Error(errorMessage);
        }
      }

      async download(_containerName: string, file: string) {
        const filePath = path.join(storageRootDir, file);
        const fileExists = await checkFileExists(file);
        if (!fileExists) {
          return new NotFound();
        }
        return new DownloadResponse([], fs.createReadStream(filePath));
      }
    },
  };
});
Example #28
Source File: ReadUrlResponseFactory.test.ts    From backstage with Apache License 2.0 4 votes vote down vote up
describe('ReadUrlResponseFactory', () => {
  describe("fromReadable's", () => {
    const expectedText = 'expected text';
    let readable: Readable;

    beforeEach(() => {
      readable = Readable.from(expectedText, {
        objectMode: false,
      });
    });

    it('etag is passed through', async () => {
      const expectedEtag = 'xyz';
      const response = await ReadUrlResponseFactory.fromReadable(readable, {
        etag: expectedEtag,
      });
      expect(response.etag).toEqual(expectedEtag);
    });

    it('buffer returns expected data', async () => {
      const response = await ReadUrlResponseFactory.fromReadable(readable);
      const buffer = await response.buffer();
      expect(buffer.toString()).toEqual(expectedText);
    });

    it('buffer can be called multiple times', async () => {
      const response = await ReadUrlResponseFactory.fromReadable(readable);
      const buffer1 = await response.buffer();
      const buffer2 = await response.buffer();
      expect(buffer1.toString()).toEqual(expectedText);
      expect(buffer2.toString()).toEqual(expectedText);
    });

    it('buffer cannot be called after stream is called', async () => {
      const response = await ReadUrlResponseFactory.fromReadable(readable);
      response.stream!();
      expect(() => response.buffer()).toThrowError(ConflictError);
    });

    it('stream returns expected data', async () => {
      const response = await ReadUrlResponseFactory.fromReadable(readable);
      const stream = response.stream!();
      const bufferFromStream = await getRawBody(stream);
      expect(bufferFromStream.toString()).toEqual(expectedText);
    });

    it('stream cannot be called after buffer is called', async () => {
      const response = await ReadUrlResponseFactory.fromReadable(readable);
      response.buffer();
      expect(() => response.stream!()).toThrowError(ConflictError);
    });
  });

  describe("fromNodeJSReadable's", () => {
    const expectedText = 'expected text';
    const timeouts: NodeJS.Timeout[] = [];
    let readable: NodeJS.ReadableStream;

    beforeEach(() => {
      // @ts-ignore
      readable = new Stream({ encoding: 'utf-8' });
      readable.readable = true;

      // Write data asynchronously, as soon as possible.
      timeouts[0] = setTimeout(() => {
        timeouts[1] = setTimeout(readable.emit.bind(readable, 'end'), 0);
        readable.emit('data', expectedText);
      }, 0);
    });

    afterEach(() => {
      // Clear out timeouts so we don't emit data across tests.
      timeouts.forEach(clearTimeout);
    });

    it('etag is passed through', async () => {
      const expectedEtag = 'xyz';
      const response = await ReadUrlResponseFactory.fromNodeJSReadable(
        readable,
        {
          etag: expectedEtag,
        },
      );
      expect(response.etag).toEqual(expectedEtag);
    });

    it('buffer returns expected data', async () => {
      const response = await ReadUrlResponseFactory.fromNodeJSReadable(
        readable,
      );
      const buffer = await response.buffer();
      expect(buffer.toString()).toEqual(expectedText);
    });

    it('stream returns expected data', async () => {
      const response = await ReadUrlResponseFactory.fromNodeJSReadable(
        readable,
      );
      const stream = response.stream!();
      const bufferFromStream = await getRawBody(stream);
      expect(bufferFromStream.toString()).toEqual(expectedText);
    });
  });
});