From 0457b84496e568e61d849200683575339af21f91 Mon Sep 17 00:00:00 2001 From: Tetsuharu Ohzeki Date: Wed, 17 Jul 2024 19:20:57 +0900 Subject: [PATCH 1/2] unittests: Allow to use `Set.prototype.isDisjointFrom()` --- packages/unittests/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/unittests/tsconfig.json b/packages/unittests/tsconfig.json index 48291a2..7a1d0f8 100644 --- a/packages/unittests/tsconfig.json +++ b/packages/unittests/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.base.project_reference.json", "compilerOptions": { + "lib": ["ESNext"], "noEmit": true, "outDir": "./__obj" }, From 59ae8cff11f23dfe2298323613f6a842d3dbe745 Mon Sep 17 00:00:00 2001 From: Tetsuharu Ohzeki Date: Wed, 17 Jul 2024 18:41:06 +0900 Subject: [PATCH 2/2] Implement functions that checks the passed http status code value is in the expected range This adds functions that checks the passed http status code value is in the expected range for `HttpStatus.***` Names of each of functions are sorted with the spec. They uses `is` convention. - [`HttpStatus.isInformational1xx`](https://httpwg.org/specs/rfc9110.html#status.1xx) - [`HttpStatus.isSuccessful2xx`](https://httpwg.org/specs/rfc9110.html#status.2xx) - [`HttpStatus.isRedirection3xx`](https://httpwg.org/specs/rfc9110.html#status.3xx) - [`HttpStatus.isClientError4xx`](https://httpwg.org/specs/rfc9110.html#status.4xx) - [`HttpStatus.isServerError5xx`](https://httpwg.org/specs/rfc9110.html#status.5xx) We take an approach for unit test that is similar to [property based testing](https://en.wikipedia.org/wiki/Software_testing#Property_testing). The test case input values that satisfies a conditions (For http status code. It's very very simple condition). And all test case files must check input patterns has no problem (no overlap, no missing spans) before running tests in them. --- packages/http-helper/src/http_status_code.ts | 70 +++++++++++++++++++ .../http_status_code.test.ts.snap | 5 ++ .../__tests__/http_status_code/helper.ts | 46 ++++++++++++ .../is_client_error_4xx.test.ts | 38 ++++++++++ .../is_informational_1xx.test.ts | 38 ++++++++++ .../is_redirection_3xx.test.ts | 38 ++++++++++ .../is_server_error_5xx.test.ts | 38 ++++++++++ .../is_successful_2xx.test.ts | 38 ++++++++++ 8 files changed, 311 insertions(+) create mode 100644 packages/unittests/__tests__/http_status_code/helper.ts create mode 100644 packages/unittests/__tests__/http_status_code/is_client_error_4xx.test.ts create mode 100644 packages/unittests/__tests__/http_status_code/is_informational_1xx.test.ts create mode 100644 packages/unittests/__tests__/http_status_code/is_redirection_3xx.test.ts create mode 100644 packages/unittests/__tests__/http_status_code/is_server_error_5xx.test.ts create mode 100644 packages/unittests/__tests__/http_status_code/is_successful_2xx.test.ts diff --git a/packages/http-helper/src/http_status_code.ts b/packages/http-helper/src/http_status_code.ts index 25621dc..550f403 100644 --- a/packages/http-helper/src/http_status_code.ts +++ b/packages/http-helper/src/http_status_code.ts @@ -276,3 +276,73 @@ export const HTTP_VERSION_NOT_SUPPORTED_505 = 505; * - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511 */ export const NETWORK_AUTHENTICATION_REQUIRED_511 = 511; + +/** + * @param code + * The http status code. + * @returns + * This will be `true` if the _code_ is in the range of [1xx][1xx]. + * Otherwise, return `false`. + * + * [1xx]: https://httpwg.org/specs/rfc9110.html#status.1xx + */ +export function isInformational1xx(code: number): boolean { + const ok = 100 <= code && code < 200; + return ok; +} + +/** + * @param code + * The http status code. + * @returns + * This will be `true` if the _code_ is in the range of [2xx][2xx]. + * Otherwise, return `false`. + * + * [2xx]: https://httpwg.org/specs/rfc9110.html#status.2xx + */ +export function isSuccessful2xx(code: number): boolean { + const ok = 200 <= code && code < 300; + return ok; +} + +/** + * @param code + * The http status code. + * @returns + * This will be `true` if the _code_ is in the range of [3xx][3xx]. + * Otherwise, return `false`. + * + * [3xx]: https://httpwg.org/specs/rfc9110.html#status.3xx + */ +export function isRedirection3xx(code: number): boolean { + const ok = 300 <= code && code < 400; + return ok; +} + +/** + * @param code + * The http status code. + * @returns + * This will be `true` if the _code_ is in the range of [4xx][4xx]. + * Otherwise, return `false`. + * + * [4xx]: https://httpwg.org/specs/rfc9110.html#status.4xx + */ +export function isClientError4xx(code: number): boolean { + const ok = 400 <= code && code < 500; + return ok; +} + +/** + * @param code + * The http status code. + * @returns + * This will be `true` if the _code_ is in the range of [5xx][5xx]. + * Otherwise, return `false`. + * + * [5xx]: https://httpwg.org/specs/rfc9110.html#status.5xx + */ +export function isServerError5xx(code: number): boolean { + const ok = 500 <= code && code < 600; + return ok; +} diff --git a/packages/unittests/__tests__/__snapshots__/http_status_code.test.ts.snap b/packages/unittests/__tests__/__snapshots__/http_status_code.test.ts.snap index 76add2b..8659f29 100644 --- a/packages/unittests/__tests__/__snapshots__/http_status_code.test.ts.snap +++ b/packages/unittests/__tests__/__snapshots__/http_status_code.test.ts.snap @@ -50,5 +50,10 @@ exports[`HttpStatus exported items 1`] = ` "UPGRADE_REQUIRED_426": 426, "URI_TOO_LONG_414": 414, "USE_PROXY_305": 305, + "isClientError4xx": [Function], + "isInformational1xx": [Function], + "isRedirection3xx": [Function], + "isServerError5xx": [Function], + "isSuccessful2xx": [Function], } `; diff --git a/packages/unittests/__tests__/http_status_code/helper.ts b/packages/unittests/__tests__/http_status_code/helper.ts new file mode 100644 index 0000000..3033510 --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/helper.ts @@ -0,0 +1,46 @@ +import { assert } from 'vitest'; + +export function assertNoOverlap(a: ReadonlySet, b: ReadonlySet): void { + const ok = a.isDisjointFrom(b); + if (!ok) { + // we want to avoid to iterate sets multiple times.... + const overlapping = a.intersection(b); + assert(false, `test cases have overlap point. They are: ${Array.from(overlapping)}`); + } +} + +const RANGE_BEGIN = 0; +// We believe the community does not inroduce new status code range over 999. +const RANGE_END = 999; + +export function assertNoLackPatterns(...ranges: Array>): void { + const base = new Set(testRange(RANGE_BEGIN, RANGE_END)); + let differ = base; + for (const range of ranges) { + differ = differ.difference(range); + } + const noDifference = differ.size === 0; + if (!noDifference) { + assert(false, `There are missing spans: ${Array.from(differ)}`); + } +} + +function* testRange(begin: number, end: number): Generator { + assert(begin <= end); + + for (let i = begin; i <= end; ++i) { + yield i; + } +} + +export function* validRange(begin: number, end: number): Generator { + yield* testRange(begin, end); +} + +export function* invalidRangeFrom0To(end: number): Generator { + yield* testRange(RANGE_BEGIN, end); +} + +export function* invalidRangeToEndFrom(begin: number): Generator { + yield* testRange(begin, RANGE_END); +} diff --git a/packages/unittests/__tests__/http_status_code/is_client_error_4xx.test.ts b/packages/unittests/__tests__/http_status_code/is_client_error_4xx.test.ts new file mode 100644 index 0000000..e449eaf --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/is_client_error_4xx.test.ts @@ -0,0 +1,38 @@ +import { test, expect, beforeAll } from 'vitest'; + +import { HttpStatus } from '@nikkei/http-helper'; +import { + assertNoLackPatterns, + assertNoOverlap, + invalidRangeFrom0To, + invalidRangeToEndFrom, + validRange, +} from './helper.js'; + +const TRUE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...validRange(400, 499), +]); + +const FALSE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...invalidRangeFrom0To(399), + ...invalidRangeToEndFrom(500), +]); + +beforeAll(() => { + assertNoOverlap(TRUE_RANGE, FALSE_RANGE); + assertNoLackPatterns(TRUE_RANGE, FALSE_RANGE); +}); + +for (const input of TRUE_RANGE) { + test(`isClientError4xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isClientError4xx(input)).toEqual(true); + }); +} + +for (const input of FALSE_RANGE) { + test(`isClientError4xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isClientError4xx(input)).toEqual(false); + }); +} diff --git a/packages/unittests/__tests__/http_status_code/is_informational_1xx.test.ts b/packages/unittests/__tests__/http_status_code/is_informational_1xx.test.ts new file mode 100644 index 0000000..1bc280c --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/is_informational_1xx.test.ts @@ -0,0 +1,38 @@ +import { test, expect, beforeAll } from 'vitest'; + +import { HttpStatus } from '@nikkei/http-helper'; +import { + assertNoLackPatterns, + assertNoOverlap, + invalidRangeFrom0To, + invalidRangeToEndFrom, + validRange, +} from './helper.js'; + +const TRUE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...validRange(100, 199), +]); + +const FALSE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...invalidRangeFrom0To(99), + ...invalidRangeToEndFrom(200), +]); + +beforeAll(() => { + assertNoOverlap(TRUE_RANGE, FALSE_RANGE); + assertNoLackPatterns(TRUE_RANGE, FALSE_RANGE); +}); + +for (const input of TRUE_RANGE) { + test(`isInformational1xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isInformational1xx(input)).toEqual(true); + }); +} + +for (const input of FALSE_RANGE) { + test(`isInformational1xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isInformational1xx(input)).toEqual(false); + }); +} diff --git a/packages/unittests/__tests__/http_status_code/is_redirection_3xx.test.ts b/packages/unittests/__tests__/http_status_code/is_redirection_3xx.test.ts new file mode 100644 index 0000000..2307a60 --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/is_redirection_3xx.test.ts @@ -0,0 +1,38 @@ +import { test, expect, beforeAll } from 'vitest'; + +import { HttpStatus } from '@nikkei/http-helper'; +import { + assertNoLackPatterns, + assertNoOverlap, + invalidRangeFrom0To, + invalidRangeToEndFrom, + validRange, +} from './helper.js'; + +const TRUE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...validRange(300, 399), +]); + +const FALSE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...invalidRangeFrom0To(299), + ...invalidRangeToEndFrom(400), +]); + +beforeAll(() => { + assertNoOverlap(TRUE_RANGE, FALSE_RANGE); + assertNoLackPatterns(TRUE_RANGE, FALSE_RANGE); +}); + +for (const input of TRUE_RANGE) { + test(`isRedirection3xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isRedirection3xx(input)).toEqual(true); + }); +} + +for (const input of FALSE_RANGE) { + test(`isRedirection3xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isRedirection3xx(input)).toEqual(false); + }); +} diff --git a/packages/unittests/__tests__/http_status_code/is_server_error_5xx.test.ts b/packages/unittests/__tests__/http_status_code/is_server_error_5xx.test.ts new file mode 100644 index 0000000..b3c19d8 --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/is_server_error_5xx.test.ts @@ -0,0 +1,38 @@ +import { test, expect, beforeAll } from 'vitest'; + +import { HttpStatus } from '@nikkei/http-helper'; +import { + assertNoLackPatterns, + assertNoOverlap, + invalidRangeFrom0To, + invalidRangeToEndFrom, + validRange, +} from './helper.js'; + +const TRUE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...validRange(500, 599), +]); + +const FALSE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...invalidRangeFrom0To(499), + ...invalidRangeToEndFrom(600), +]); + +beforeAll(() => { + assertNoOverlap(TRUE_RANGE, FALSE_RANGE); + assertNoLackPatterns(TRUE_RANGE, FALSE_RANGE); +}); + +for (const input of TRUE_RANGE) { + test(`isServerError5xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isServerError5xx(input)).toEqual(true); + }); +} + +for (const input of FALSE_RANGE) { + test(`isServerError5xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isServerError5xx(input)).toEqual(false); + }); +} diff --git a/packages/unittests/__tests__/http_status_code/is_successful_2xx.test.ts b/packages/unittests/__tests__/http_status_code/is_successful_2xx.test.ts new file mode 100644 index 0000000..22e7c63 --- /dev/null +++ b/packages/unittests/__tests__/http_status_code/is_successful_2xx.test.ts @@ -0,0 +1,38 @@ +import { test, expect, beforeAll } from 'vitest'; + +import { HttpStatus } from '@nikkei/http-helper'; +import { + assertNoLackPatterns, + assertNoOverlap, + invalidRangeFrom0To, + invalidRangeToEndFrom, + validRange, +} from './helper.js'; + +const TRUE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...validRange(200, 299), +]); + +const FALSE_RANGE: ReadonlySet = new Set([ + // @prettier-ignore + ...invalidRangeFrom0To(199), + ...invalidRangeToEndFrom(300), +]); + +beforeAll(() => { + assertNoOverlap(TRUE_RANGE, FALSE_RANGE); + assertNoLackPatterns(TRUE_RANGE, FALSE_RANGE); +}); + +for (const input of TRUE_RANGE) { + test(`isSuccessful2xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isSuccessful2xx(input)).toEqual(true); + }); +} + +for (const input of FALSE_RANGE) { + test(`isSuccessful2xx(): input is \`${String(input)}\``, () => { + expect(HttpStatus.isSuccessful2xx(input)).toEqual(false); + }); +}