Skip to content

Commit

Permalink
test(core): added tests to is binary file
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Mar 1, 2022
1 parent 23952db commit 1ad531f
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 44 deletions.
42 changes: 22 additions & 20 deletions src/v2/@types/binary-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@
*
* @note Encoded as binary means the response body will be converted to base64
*/
export interface BinarySettings {
/**
* This property can be a function that receives the response headers and returns whether that response should be encoded as binary.
* Otherwise, you can specify not to treat any response as binary by putting `false` in this property.
*
* @note Setting this property prevents the `contentTypes` and `contentEncodings` properties from being used.
*/
isBinary?:
| ((headers: Record<string, string | string[] | undefined>) => boolean)
| false;
export type BinarySettings =
| {
/**
* This property can be a function that receives the response headers and returns whether that response should be encoded as binary.
* Otherwise, you can specify not to treat any response as binary by putting `false` in this property.
*
* @note Setting this property prevents the `contentTypes` and `contentEncodings` properties from being used.
*/
isBinary:
| ((headers: Record<string, string | string[] | undefined>) => boolean)
| false;
}
| {
/**
* The list of content types that will be treated as binary
*/
contentTypes: (string | RegExp)[];

/**
* The list of content types that will be treated as binary
*/
contentTypes: string[];

/**
* The list of content encodings that will be treated as binary
*/
contentEncodings: string[];
}
/**
* The list of content encodings that will be treated as binary
*/
contentEncodings: string[];
};
8 changes: 6 additions & 2 deletions src/v2/core/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export const DEFAULT_BINARY_ENCODINGS = ['gzip', 'deflate', 'br'];
export const DEFAULT_BINARY_CONTENT_TYPES = ['image/*'];
export const DEFAULT_BINARY_ENCODINGS: string[] = ['gzip', 'deflate', 'br'];
export const DEFAULT_BINARY_CONTENT_TYPES: (string | RegExp)[] = [
new RegExp('^image/.*$'),
new RegExp('^video/.*$'),
'application/pdf',
];

// eslint-disable-next-line @typescript-eslint/ban-types
export type IEmptyResponse = {};
Expand Down
70 changes: 48 additions & 22 deletions src/v2/core/is-binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,53 @@
//#region Imports

import { BinarySettings, BothValueHeaders } from '../@types';
import { getFlattenedHeadersMap, getMultiValueHeadersMap } from './headers';

//#endregion

/**
* The function that determines by the content encoding whether the response should be treated as binary
*
* @example```typescript
* const headers = { 'Content-Encoding': 'gzip' };
* const isBinary = isContentEncodingBinary(headers, ['gzip']);
* console.log(isBinary);
* // true
* ```
*
* @param headers The headers of the response
* @param binaryEncodingTypes The list of content encodings that will be treated as binary
*/
export function isContentEncodingBinary(
headers: BothValueHeaders,
binaryEncodingTypes: string[],
): boolean {
const contentEncoding = Array.isArray(headers['content-encoding'])
? headers['content-encoding'][0]
: headers['content-encoding'];

if (typeof contentEncoding !== 'string') return false;

return contentEncoding
.split(',')
.some(value =>
binaryEncodingTypes.some(binaryEncoding =>
value.includes(binaryEncoding),
),
);
const multiValueHeaders = getMultiValueHeadersMap(headers);

const contentEncodings = multiValueHeaders['content-encoding'];

if (!Array.isArray(contentEncodings)) return false;

return contentEncodings.some(value =>
binaryEncodingTypes.some(binaryEncoding => value.includes(binaryEncoding)),
);
}

/**
* The function that returns the content type of headers
*
* @example```typescript
* const headers = { 'Content-Type': 'application/json' };
* const contentType = getContentType(headers);
* console.log(contentType);
* // application/json
* ```
*
* @param headers The headers of the response
*/
export function getContentType(headers: BothValueHeaders): string {
const contentTypeHeader = Array.isArray(headers['content-type'])
? headers['content-type'][0] || ''
: headers['content-type'] || '';
const flattenedHeaders = getFlattenedHeadersMap(headers, ';', true);
const contentTypeHeader = flattenedHeaders['content-type'] || '';

// only compare mime type; ignore encoding part
return contentTypeHeader.split(';')[0];
Expand All @@ -48,17 +58,26 @@ export function getContentType(headers: BothValueHeaders): string {
/**
* The function that determines by the content type whether the response should be treated as binary
*
* @example```typescript
* const headers = { 'Content-Type': 'image/png' };
* const isBinary = isContentTypeBinary(headers, [new RegExp('^image/.*$')]);
* console.log(isBinary);
* // true
* ```
*
* @param headers The headers of the response
* @param binaryContentTypes The list of content types that will be treated as binary
*/
export function isContentTypeBinary(
headers: BothValueHeaders,
binaryContentTypes: string[],
binaryContentTypes: (string | RegExp)[],
) {
const binaryContentTypesRegexes = binaryContentTypes.map(
binaryContentType =>
new RegExp(`^${binaryContentType.replace(/\*/g, '.*')}$`),
const binaryContentTypesRegexes = binaryContentTypes.map(binaryContentType =>
binaryContentType instanceof RegExp
? binaryContentType
: new RegExp(`${binaryContentType}`),
);

const contentType = getContentType(headers);

if (!contentType) return false;
Expand All @@ -71,17 +90,24 @@ export function isContentTypeBinary(
/**
* The function used to determine from the headers and the binary settings if a response should be encoded or not
*
* @example```typescript
* const headers = { 'Content-Type': 'image/png', 'Content-Encoding': 'gzip' };
* const isContentBinary = isBinary(headers, { contentEncodings: ['gzip'], contentTypes: [new RegExp('^image/.*$')] });
* console.log(isContentBinary);
* // true
*
* @param headers The headers of the response
* @param binarySettings The settings for the validation
*/
export function isBinary(
headers: BothValueHeaders,
binarySettings: BinarySettings,
): boolean {
if (binarySettings.isBinary === false) return false;
if ('isBinary' in binarySettings) {
if (binarySettings.isBinary === false) return false;

if (typeof binarySettings.isBinary === 'function')
return binarySettings.isBinary(headers);
}

return (
isContentEncodingBinary(headers, binarySettings.contentEncodings) ||
Expand Down
150 changes: 150 additions & 0 deletions test/core/is-binary.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { BothValueHeaders } from '../../src/v2/@types';
import {
DEFAULT_BINARY_CONTENT_TYPES,
DEFAULT_BINARY_ENCODINGS,
getContentType,
isBinary,
isContentEncodingBinary,
isContentTypeBinary,
} from '../../src/v2/core';

type HeaderListJest = [headers: BothValueHeaders, expectedValue: boolean][];

const headerListForContentEncodings: HeaderListJest = [
[{ 'Content-Encoding': undefined }, false],
[{ 'Content-Encoding': [] }, false],
[{ 'Content-Encoding': 'non-standard' }, false],
[{ 'Content-Encoding': ['non-standard'] }, false],
[{ 'Content-Encoding': 'gzip' }, true],
[{ 'Content-Encoding': 'deflate' }, true],
[{ 'Content-Encoding': 'br' }, true],
[{ 'Content-Encoding': 'gzip,non-standard' }, true],
[{ 'Content-Encoding': ['gzip'] }, true],
[{ 'Content-Encoding': ['gzip', 'non-standard'] }, true],
[{ 'Content-Encoding': ['deflate'] }, true],
[{ 'Content-Encoding': ['deflate', 'non-standard'] }, true],
[{ 'Content-Encoding': ['br'] }, true],
[{ 'Content-Encoding': ['br', 'non-standard'] }, true],
];

const headerListForContentTypes: HeaderListJest = [
[{ 'Content-Type': undefined }, false],
[{ 'Content-Type': [] }, false],
[{ 'Content-Type': 'application/json' }, false],
[{ 'Content-Type': ['application/json'] }, false],
[{ 'Content-Type': 'application/json,image/png' }, false],
[{ 'Content-Type': 'application/json;image/png' }, false],
[{ 'Content-Type': ['application/json', 'image/png'] }, false],
[{ 'Content-Type': 'image/png' }, true],
[{ 'Content-Type': ['image/png'] }, true],
[{ 'Content-Type': 'video/mp4' }, true],
[{ 'Content-Type': ['video/mp4'] }, true],
[{ 'Content-Type': 'application/pdf' }, true],
[{ 'Content-Type': ['application/pdf'] }, true],
];

describe('isContentEncodingBinary', () => {
it('should correctly check if content encoding is binary', () => {
const headersList: HeaderListJest = [
[{ 'Content-Type': 'application/json' }, false],
...headerListForContentEncodings,
];

const binaryEncodings = DEFAULT_BINARY_ENCODINGS;

for (const [headers, expectedValue] of headersList) {
const isBinary = isContentEncodingBinary(headers, binaryEncodings);

expect(isBinary).toBe(expectedValue);
}
});
});

describe('getContentType', () => {
it('should correctly return the content type from headers', () => {
const headersList: [headers: BothValueHeaders, expectedValue: string][] = [
[{ 'Content-Encoding': 'gzip' }, ''],
[{ 'Content-Type': 'application/json' }, 'application/json'],
[{ 'Content-Type': ['application/json'] }, 'application/json'],
[
{ 'Content-Type': 'application/json,image/png' },
'application/json,image/png',
],
[{ 'Content-Type': 'application/json;image/png' }, 'application/json'],
[
{ 'Content-Type': ['application/json', 'image/png'] },
'application/json',
],
[{ 'Content-Type': ['image/png', 'application/json'] }, 'image/png'],
];

for (const [headers, expectedValue] of headersList) {
const isBinary = getContentType(headers);

expect(isBinary).toBe(expectedValue);
}
});
});

describe('isContentTypeBinary', () => {
it('should correctly check if content type is binary', () => {
const headersList: [headers: BothValueHeaders, expectedValue: boolean][] = [
[{ 'Content-Encoding': 'gzip' }, false],
...headerListForContentTypes,
];

const binaryEncodings = DEFAULT_BINARY_CONTENT_TYPES;

for (const [headers, expectedValue] of headersList) {
const isBinary = isContentTypeBinary(headers, binaryEncodings);

expect(isBinary).toBe(expectedValue);
}
});
});

describe('isBinary', () => {
it('should correctly return if content is binary', () => {
const headersList: [headers: BothValueHeaders, expectedValue: boolean][] = [
[{ Host: 'blablabla.com' }, false],
...headerListForContentEncodings,
...headerListForContentTypes,
];

const contentTypes = DEFAULT_BINARY_CONTENT_TYPES;
const contentEncodings = DEFAULT_BINARY_ENCODINGS;

for (const [headers, expectedValue] of headersList) {
const isContentBinary = isBinary(headers, {
contentTypes,
contentEncodings,
});

expect(isContentBinary).toBe(expectedValue);
}
});

it('should correctly return if content is binary with custom "isBinary" option', () => {
const headersList: [headers: BothValueHeaders, expectedValue: boolean][] = [
[{ Host: 'blablabla.com' }, false],
...headerListForContentEncodings,
...headerListForContentTypes,
];

for (const [headers] of headersList) {
const isContentBinary = isBinary(headers, {
isBinary: () => true,
});

expect(isContentBinary).toBe(true);
}

for (const [headers] of headersList) {
const isContentBinary = isBinary(headers, {
isBinary: false,
});

expect(isContentBinary).toBe(false);
}
});
});

0 comments on commit 1ad531f

Please sign in to comment.