Skip to content

Commit

Permalink
lots of improvements!
Browse files Browse the repository at this point in the history
  • Loading branch information
jollytoad committed May 14, 2024
1 parent 475a753 commit 27b1365
Show file tree
Hide file tree
Showing 53 changed files with 803 additions and 238 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
deno.lock
coverage
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
"deno.lint": true,
"deno.importMap": "./import_map_local.json",
"editor.defaultFormatter": "denoland.vscode-deno",
"editor.tabSize": 2
"editor.tabSize": 2,
"deno.codeLens.testArgs": [
"--allow-all",
"--no-check",
"--location=http://localhost:8000"
]
}
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ and this project adheres to

## [Unreleased]

This changelog will need to be split between individual packages

## [0.12.0]

### Changed

- [@http/interceptor] `intercept()` error handlers applied at each lifecycle
stage, and no longer implicitly handles thrown Responses
- [@http/interceptor] `interceptResponse()` moved into own module
- [@http/interceptor] `skip()` moved into own module
- [@http/request] utility functions now take a Request as first parameter rather
than returning a function

### Added

- [@http/interceptor] `catchResponse()` to explicitly handle thrown Responses
- [@http/interceptor] tests added
- [@http/request] `despose()` added to `memoize` module
- [@http/assert] `assertHeader()` now supports `Request` and `Headers` object

## [0.10.0]

### Changed
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion scripts/fix-imports.ts → _tools/fix-imports.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { walk } from "jsr:@std/fs@^0.221.0/walk";
import { walk } from "@std/fs/walk";

export async function fixStdLibImports(root: string) {
for await (const entry of walk(root, { exts: [".ts"] })) {
Expand Down
2 changes: 2 additions & 0 deletions scripts/local-import-map.ts → _tools/local-import-map.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env -S deno run --allow-read=. --allow-write=.

import { resolve } from "@std/path/resolve";
import { join } from "@std/path/posix/join";
import { readDenoConfig, readJson, rootPath, writeJson } from "./_utils.ts";
Expand Down
2 changes: 2 additions & 0 deletions scripts/packages.ts → _tools/packages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env -S deno run --allow-read=. --allow-write=.

import { resolve } from "@std/path/resolve";
import { join } from "@std/path/join";
import { join as posixJoin } from "@std/path/posix/join";
Expand Down
51 changes: 26 additions & 25 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"tasks": {
"example": "deno run -A --import-map=./import_map_local.json --watch",
"test": "deno test -A --import-map=./import_map_local.json --location=http://localhost:8000 --no-check",
"check": "deno fmt && deno lint && deno check **/*.ts && deno task packages && deno task local-import-map",
"packages": "deno run --allow-read=. --allow-write=. scripts/packages.ts",
"local-import-map": "deno run --allow-read=. --allow-write=. scripts/local-import-map.ts",
"test": "deno test -A --import-map=./import_map_local.json --location=http://localhost:8000 --coverage --no-check",
"ok": "deno fmt && deno lint && deno task packages && deno task local-import-map && deno check --import-map=./import_map_local.json **/*.ts && deno task test && deno publish --dry-run --allow-dirty",
"packages": "./_tools/packages.ts",
"local-import-map": "./_tools/local-import-map.ts",
"outdated": "deno run --allow-read=. --allow-net=jsr.io,registry.npmjs.org jsr:@check/deps",
"lock": "rm -f deno.lock && deno check **/*.ts"
},
Expand All @@ -13,27 +13,28 @@
"verbatimModuleSyntax": true
},
"imports": {
"@http/assert": "jsr:@http/assert@0.10.0",
"@http/discovery": "jsr:@http/discovery@0.10.0",
"@http/examples": "jsr:@http/examples@0.10.0",
"@http/generate": "jsr:@http/generate@0.10.0",
"@http/handler": "jsr:@http/handler@0.10.0",
"@http/host-deno-deploy": "jsr:@http/host-deno-deploy@0.10.0",
"@http/host-deno-local": "jsr:@http/host-deno-local@0.10.0",
"@http/interceptor": "jsr:@http/interceptor@0.10.0",
"@http/request": "jsr:@http/request@0.10.0",
"@http/response": "jsr:@http/response@0.10.0",
"@http/route": "jsr:@http/route@0.10.0",
"@http/route-deno": "jsr:@http/route-deno@0.10.0",
"@std/assert": "jsr:@std/assert@^0.223.0",
"@std/collections": "jsr:@std/collections@^0.223.0",
"@std/http": "jsr:@std/http@^0.223.0",
"@std/media-types": "jsr:@std/media-types@^0.223.0",
"@std/net": "jsr:@std/net@^0.223.0",
"@std/path": "jsr:@std/path@^0.223.0",
"@std/testing": "jsr:@std/testing@^0.223.0",
"@std/url": "jsr:@std/url@^0.223.0",
"ts-poet": "npm:ts-poet@^6.8.1"
"@http/assert": "jsr:@http/assert@0.12.0",
"@http/discovery": "jsr:@http/discovery@0.12.0",
"@http/examples": "jsr:@http/examples@0.12.0",
"@http/generate": "jsr:@http/generate@0.12.0",
"@http/handler": "jsr:@http/handler@0.12.0",
"@http/host-deno-deploy": "jsr:@http/host-deno-deploy@0.12.0",
"@http/host-deno-local": "jsr:@http/host-deno-local@0.12.0",
"@http/interceptor": "jsr:@http/interceptor@0.12.0",
"@http/request": "jsr:@http/request@0.12.0",
"@http/response": "jsr:@http/response@0.12.0",
"@http/route": "jsr:@http/route@0.12.0",
"@http/route-deno": "jsr:@http/route-deno@0.12.0",
"@std/assert": "jsr:@std/assert@^0.225.1",
"@std/collections": "jsr:@std/collections@^0.224.0",
"@std/fs": "jsr:@std/fs@^0.224.0",
"@std/http": "jsr:@std/http@^0.224.0",
"@std/media-types": "jsr:@std/media-types@^0.224.0",
"@std/net": "jsr:@std/net@^0.224.0",
"@std/path": "jsr:@std/path@^0.224.0",
"@std/testing": "jsr:@std/testing@^0.224.0",
"@std/url": "jsr:@std/url@^0.224.0",
"ts-poet": "npm:ts-poet@^6.9.0"
},
"workspaces": [
"./packages/assert",
Expand Down
41 changes: 23 additions & 18 deletions import_map_local.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
"@http/host-deno-local/port": "./packages/host-deno-local/port.ts",
"@http/host-deno-local/server-url": "./packages/host-deno-local/server_url.ts",
"@http/interceptor/by-status": "./packages/interceptor/by_status.ts",
"@http/interceptor/catch-response": "./packages/interceptor/catch_response.ts",
"@http/interceptor/cors": "./packages/interceptor/cors.ts",
"@http/interceptor/intercept": "./packages/interceptor/intercept.ts",
"@http/interceptor/intercept-response": "./packages/interceptor/intercept_response.ts",
"@http/interceptor/logger": "./packages/interceptor/logger.ts",
"@http/interceptor/skip": "./packages/interceptor/skip.ts",
"@http/interceptor/types": "./packages/interceptor/types.ts",
"@http/interceptor/verify-header": "./packages/interceptor/verify_header.ts",
"@http/interceptor/when-pattern": "./packages/interceptor/when_pattern.ts",
Expand Down Expand Up @@ -81,23 +84,25 @@
"@http/route/handle": "./packages/route/handle.ts",
"@http/route/types": "./packages/route/types.ts",
"@http/route/with-fallback": "./packages/route/with_fallback.ts",
"@std/assert": "jsr:@std/assert@^0.223.0",
"@std/assert/": "jsr:/@std/assert@^0.223.0/",
"@std/collections": "jsr:@std/collections@^0.223.0",
"@std/collections/": "jsr:/@std/collections@^0.223.0/",
"@std/http": "jsr:@std/http@^0.223.0",
"@std/http/": "jsr:/@std/http@^0.223.0/",
"@std/media-types": "jsr:@std/media-types@^0.223.0",
"@std/media-types/": "jsr:/@std/media-types@^0.223.0/",
"@std/net": "jsr:@std/net@^0.223.0",
"@std/net/": "jsr:/@std/net@^0.223.0/",
"@std/path": "jsr:@std/path@^0.223.0",
"@std/path/": "jsr:/@std/path@^0.223.0/",
"@std/testing": "jsr:@std/testing@^0.223.0",
"@std/testing/": "jsr:/@std/testing@^0.223.0/",
"@std/url": "jsr:@std/url@^0.223.0",
"@std/url/": "jsr:/@std/url@^0.223.0/",
"ts-poet": "npm:ts-poet@^6.8.1",
"ts-poet/": "npm:/ts-poet@^6.8.1/"
"@std/assert": "jsr:@std/assert@^0.225.1",
"@std/assert/": "jsr:/@std/assert@^0.225.1/",
"@std/collections": "jsr:@std/collections@^0.224.0",
"@std/collections/": "jsr:/@std/collections@^0.224.0/",
"@std/fs": "jsr:@std/fs@^0.224.0",
"@std/fs/": "jsr:/@std/fs@^0.224.0/",
"@std/http": "jsr:@std/http@^0.224.0",
"@std/http/": "jsr:/@std/http@^0.224.0/",
"@std/media-types": "jsr:@std/media-types@^0.224.0",
"@std/media-types/": "jsr:/@std/media-types@^0.224.0/",
"@std/net": "jsr:@std/net@^0.224.0",
"@std/net/": "jsr:/@std/net@^0.224.0/",
"@std/path": "jsr:@std/path@^0.224.0",
"@std/path/": "jsr:/@std/path@^0.224.0/",
"@std/testing": "jsr:@std/testing@^0.224.0",
"@std/testing/": "jsr:/@std/testing@^0.224.0/",
"@std/url": "jsr:@std/url@^0.224.0",
"@std/url/": "jsr:/@std/url@^0.224.0/",
"ts-poet": "npm:ts-poet@^6.9.0",
"ts-poet/": "npm:/ts-poet@^6.9.0/"
}
}
16 changes: 15 additions & 1 deletion packages/assert/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# HTTP Assertions

_Docs coming soon_
Assertion functions for use in HTTP Request/Response related tests.

An [AssertionError](https://jsr.io/@std/assert/doc/~/AssertionError) will be
thrown for false assertions.

```ts
import { assertStatus, STATUS_CODE } from "@http/assert";

const response = new Response();

assertStatus(response, STATE_CODE.OK);
```

NOTE: the `STATUS_CODE` object and `StatusCode` type are re-exported from
`@std/http/status` for convenience.
51 changes: 51 additions & 0 deletions packages/assert/assert_header.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { assertHeader } from "./assert_header.ts";
import { AssertionError } from "@std/assert/assertion-error";
import { assertThrows } from "@std/assert/assert-throws";

Deno.test("assertHeader() matches header with value in Headers object", () => {
const headers = new Headers({ "Stuff": "Nonsense" });
assertHeader(headers, "Stuff", "Nonsense");
});

Deno.test("assertHeader() matches header with value in Request object", () => {
const request = new Request("http://example.com", {
headers: { "Stuff": "Nonsense" },
});
assertHeader(request, "Stuff", "Nonsense");
});

Deno.test("assertHeader() matches header with value in Response object", () => {
const response = new Response("http://example.com", {
headers: { "Stuff": "Nonsense" },
});
assertHeader(response, "Stuff", "Nonsense");
});

Deno.test("assertHeader() matches header name case-insensitively", () => {
const headers = new Headers({ "Stuff": "Nonsense" });
assertHeader(headers, "stuFF", "Nonsense");
});

Deno.test("assertHeader() throws when header is missing", () => {
const headers = new Headers({ "Not-Stuff": "Nonsense" });
assertThrows(
() => {
assertHeader(headers, "Stuff", "Nonsense");
},
AssertionError,
undefined,
`Expected header "Stuff" with value "Nonsense", header not present`,
);
});

Deno.test("assertHeader() throws when value is not equal", () => {
const headers = new Headers({ "Stuff": "Nonsense" });
assertThrows(
() => {
assertHeader(headers, "Stuff", "Sense");
},
AssertionError,
undefined,
`Expected header "Stuff" with value "Sense", got: "Nonsense"`,
);
});
32 changes: 27 additions & 5 deletions packages/assert/assert_header.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import { AssertionError } from "@std/assert/assertion-error";

/**
* Assert that a HTTP header is present with an expected value.
*
* @param container a Request, Response, or Headers object
* @param headerName the header name
* @param expectedValue the value expected in the header
* @throws an AssertionError if the header is missing or its value is not exactly equal to the expected value
*
* @example
* ```ts
* import { assertHeader } from "@http/assert";
*
* const resp = new Response(null, {
* headers: {
* "Content-Type": "application/json"
* }
* });
*
* assertHeader(resp, "Content-Type", "application/json");
* ```
*/
export function assertHeader(
response: Response,
container: Response | Request | Headers,
headerName: string,
expectedValue: string,
) {
if (!response.headers.has(headerName)) {
const headers = container instanceof Headers ? container : container.headers;
if (!headers.has(headerName)) {
throw new AssertionError(
`Expected response header "${headerName}" with value "${expectedValue}", but header not present`,
`Expected header "${headerName}" with value "${expectedValue}", but header not present`,
);
}

const actualValue = response.headers.get(headerName);
const actualValue = headers.get(headerName);
if (actualValue !== expectedValue) {
throw new AssertionError(
`Expected response header "${headerName}" with value "${expectedValue}", got: "${actualValue}"`,
`Expected header "${headerName}" with value "${expectedValue}", got: "${actualValue}"`,
);
}
}
29 changes: 29 additions & 0 deletions packages/assert/assert_ok.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { assertOk } from "./assert_ok.ts";
import { assertThrows } from "@std/assert/assert-throws";
import { AssertionError } from "@std/assert/assertion-error";

Deno.test("assertOk() matches when response.ok is true", () => {
assertOk(new Response());
});

Deno.test("assertOk() throws when response.ok is false", () => {
assertThrows(
() => {
assertOk(new Response(null, { status: 500 }));
},
AssertionError,
"Expected response ok",
);
});

Deno.test("assertOk() thrown error includes status code and text", () => {
assertThrows(
() => {
assertOk(
new Response(null, { status: 599, statusText: "Naughty naughty" }),
);
},
AssertionError,
"599 Naughty naughty",
);
});
13 changes: 13 additions & 0 deletions packages/assert/assert_ok.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { AssertionError } from "@std/assert/assertion-error";

/**
* Assert that a Response is `ok`.
*
* @param response the Response object to test
* @throws an AssertionError if the `ok` property of the Response was not true
*
* @example
* ```ts
* import { assertOk } from "@http/assert";
*
* assertOk(new Response());
* ```
*/
export function assertOk(response: Response) {
if (!response.ok) {
throw new AssertionError(
Expand Down
31 changes: 31 additions & 0 deletions packages/assert/assert_status.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { assertStatus, STATUS_CODE } from "./assert_status.ts";
import { assertThrows } from "@std/assert/assert-throws";
import { AssertionError } from "@std/assert/assertion-error";

Deno.test("assertStatus() matches status code and text", () => {
assertStatus(new Response(null, { status: 200, statusText: "OK" }), 200);
});

Deno.test("assertStatus() throws status code doesn't match", () => {
assertThrows(
() => {
assertStatus(new Response(null, { status: 500 }), 200);
},
AssertionError,
"Expected response status",
);
});

Deno.test("assertStatus() throws if status text doesn't match the expected value for the code", () => {
assertThrows(
() => {
assertStatus(
new Response(null, { status: 200, statusText: "Alrighty" }),
STATUS_CODE.OK,
);
},
AssertionError,
undefined,
`"Expected response status "200 OK", got "200 Alrighty"`,
);
});
Loading

0 comments on commit 27b1365

Please sign in to comment.