summaryrefslogtreecommitdiff
path: root/tests/js/hero.mjs
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-02-23 06:05:19 -0300
committerEuAndreh <eu@euandre.org>2024-02-23 06:05:21 -0300
commitc36bf8e3577da31cf6d575879c7e92d3e9c7e4f1 (patch)
tree4fd5414e76490e297c4c770ff35e09149ef3658f /tests/js/hero.mjs
parentRemove C code and cleanup repository (diff)
downloadpapod-c36bf8e3577da31cf6d575879c7e92d3e9c7e4f1.tar.gz
papod-c36bf8e3577da31cf6d575879c7e92d3e9c7e4f1.tar.xz
Big cleanup
- delete all SQLite Node-API code: we'll use the C++ one instead; - implement hero.mjs, with tests! - use ESM all over.
Diffstat (limited to 'tests/js/hero.mjs')
-rw-r--r--tests/js/hero.mjs1050
1 files changed, 1050 insertions, 0 deletions
diff --git a/tests/js/hero.mjs b/tests/js/hero.mjs
new file mode 100644
index 0000000..09e6e54
--- /dev/null
+++ b/tests/js/hero.mjs
@@ -0,0 +1,1050 @@
+import assert from "node:assert/strict";
+import { runTests } from "../runner.mjs";
+import { assocIn } from "../../src/utils.mjs";
+import {
+ normalizeSegments,
+ pathToSegments,
+ hasPathParams,
+ addRoute,
+ buildRoutes,
+ findStaticHandler,
+ firstParamMatch,
+ findDynamicHandler,
+ findHandler,
+ extractQueryParams,
+ handleRequest,
+ makeRequestListener,
+ interceptorsFn,
+ chainInterceptors,
+ wrapHandler,
+} from "../../src/hero.mjs";
+
+
+const test_normalizeSegments = t => {
+ t.start("normalizeSegments()");
+
+ t.test("unchanged when already normalized", () => {
+ assert.deepEqual(normalizeSegments([""]), [""]);
+ });
+
+ t.test("empty terminator added when missing", () => {
+ assert.deepEqual(normalizeSegments([]), [""]);
+ assert.deepEqual(normalizeSegments([" "]), [" ", ""]);
+ assert.deepEqual(normalizeSegments(["_"]), ["_", ""]);
+ assert.deepEqual(normalizeSegments(["a"]), ["a", ""]);
+ assert.deepEqual(normalizeSegments([":a"]), [":a", ""]);
+ assert.deepEqual(normalizeSegments(["path"]), ["path", ""]);
+ });
+};
+
+const test_pathToSegments = t => {
+ t.start("pathToSegments()");
+
+ t.test("simple paths", () => {
+ assert.deepEqual(pathToSegments("/"), [ "" ]);
+ assert.deepEqual(pathToSegments("/simple"), ["simple", ""]);
+ assert.deepEqual(pathToSegments("/simple/route"), ["simple", "route", ""]);
+
+ assert.deepEqual(pathToSegments("/api/user/:id"), ["api", "user", ":id", ""]);
+ assert.deepEqual(pathToSegments("/api/user/:id/info"), ["api", "user", ":id", "info", ""]);
+ });
+
+ t.test("extra separators", () => {
+ assert.deepEqual(pathToSegments("/api/health/"), ["api", "health", ""]);
+ assert.deepEqual(pathToSegments("/api///health"), ["api", "health", ""]);
+ assert.deepEqual(pathToSegments("//api///health//"), ["api", "health", ""]);
+ });
+};
+
+const test_hasPathParams = t => {
+ t.start("hasPathParam()");
+
+ t.test("has it", () => {
+ assert(hasPathParams(["some", ":path", ""]));
+ assert(hasPathParams(["path", ":params", ""]));
+ assert(hasPathParams(["api", "user", ":id", "info", ""]));
+ });
+
+ t.test("doesn't have it", () => {
+ assert(!hasPathParams([]));
+ assert(!hasPathParams([ "" ]));
+ assert(!hasPathParams(["some", "path", ""]));
+ assert(!hasPathParams(["s:o:m:e:", "p::ath:::"]));
+ assert(!hasPathParams([" :"]));
+ });
+};
+
+const test_addRoute = t => {
+ t.start("addRoute()");
+
+ const fn1 = () => {};
+
+ t.test("daily usage examples", () => {
+ assert.deepEqual(
+ addRoute({}, "GET", "/home", fn1),
+ { static: { GET: { home: { "": fn1 }}}},
+ );
+
+ assert.deepEqual(
+ addRoute({}, ["PUT", "POST", "PATCH"], "/api/user", fn1),
+ {
+ static: {
+ PUT: { api: { user: { "": fn1 }}},
+ POST: { api: { user: { "": fn1 }}},
+ PATCH: { api: { user: { "": fn1 }}},
+ },
+ },
+ );
+
+ assert.deepEqual(
+ addRoute({}, "*", "/settings", fn1),
+ {
+ static: {
+ GET: { settings: { "": fn1 }},
+ HEAD: { settings: { "": fn1 }},
+ POST: { settings: { "": fn1 }},
+ PUT: { settings: { "": fn1 }},
+ PATCH: { settings: { "": fn1 }},
+ DELETE: { settings: { "": fn1 }},
+ OPTIONS: { settings: { "": fn1 }},
+ },
+ },
+ );
+
+ assert.deepEqual(
+ addRoute({}, "GET", "/", fn1),
+ { static: { GET: { "": fn1 }}},
+ );
+
+ assert.deepEqual(
+ addRoute({}, "GET", "/api/user/:id", fn1),
+ { dynamic: { GET: { api: { user: { ":id": { "": fn1 }}}}}},
+ );
+
+ assert.deepEqual(
+ addRoute({}, ["PUT", "PATCH"], "/api/org/:orgid/member/:memberid", fn1),
+ {
+ dynamic: {
+ PUT: { api: { org: { ":orgid": { member: { ":memberid": { "": fn1 }}}}}},
+ PATCH: { api: { org: { ":orgid": { member: { ":memberid": { "": fn1 }}}}}},
+ },
+ },
+ );
+ });
+
+ t.test("bad method", () => {
+ assert.throws(
+ () => addRoute({}, "VERB", "/path", fn1),
+ assert.AssertionError,
+ );
+ });
+
+ t.test("empty methods array", () => {
+ assert.deepEqual(addRoute({}, [], "", fn1), {});
+ });
+
+ t.test("subpaths", () => {
+ const fn1 = () => {};
+ const fn2 = () => {};
+
+ const t1 = addRoute({}, "GET", "/home", fn1);
+ const t2 = addRoute(t1, "GET", "/home/details", fn2);
+ assert.deepEqual(
+ t2,
+ {
+ static: {
+ GET: {
+ home: {
+ "": fn1,
+ details: {
+ "": fn2,
+ },
+ },
+ },
+ },
+ },
+ );
+ });
+};
+
+const test_buildRoutes = t => {
+ t.start("buildRoutes()");
+
+ t.test("empty values", () => {
+ assert.deepEqual(buildRoutes([]), {});
+ });
+
+ t.test("overwrites", () => {
+ const fn1 = () => {};
+ const fn2 = () => {};
+
+ const r1 = [ [ "GET", "/", fn1 ] ];
+ const r2 = [ [ "GET", "/", fn2 ] ];
+
+ assert.deepEqual(
+ buildRoutes(r1),
+ {
+ static: {
+ GET: {
+ "": fn1,
+ },
+ },
+ },
+ );
+
+ assert.deepEqual(
+ buildRoutes(r2),
+ {
+ static: {
+ GET: {
+ "": fn2,
+ },
+ },
+ },
+ );
+
+ assert.deepEqual(
+ buildRoutes(r1.concat(r2)),
+ buildRoutes(r2),
+ );
+
+ assert.deepEqual(
+ buildRoutes(r2.concat(r1)),
+ buildRoutes(r1),
+ );
+ });
+
+ t.test("multiple routes built", () => {
+ const fn1 = () => {};
+ const fn2 = () => {};
+ const fn3 = () => {};
+ const fn4 = () => {};
+ const fn5 = () => {};
+
+ const routes = [
+ [ "GET", "/", fn1 ],
+ [ "GET", "/home", fn2 ],
+ [ "GET", "/user", fn3 ],
+ [ "POST", "/user", fn4 ],
+ [ "GET", "/user/:id", fn5 ],
+ ];
+
+ assert.deepEqual(
+ buildRoutes(routes),
+ {
+ static: {
+ GET: {
+ "": fn1,
+ home: {
+ "": fn2,
+ },
+ user: {
+ "": fn3,
+ },
+ },
+ POST: {
+ user: {
+ "": fn4,
+ },
+ },
+ },
+ dynamic: {
+ GET: {
+ user: {
+ ":id": {
+ "": fn5,
+ }
+ }
+ }
+ },
+ },
+ );
+ });
+};
+
+const test_findStaticHandler = t => {
+ t.start("findStaticHandler()");
+
+ t.test("multiple accesses to the same table", () => {
+ const fn1 = () => {};
+ const fn2 = () => {};
+ const fn3 = () => {};
+
+ const table = {
+ static: {
+ GET: {
+ api: {
+ home: { "": fn1 },
+ settings: { "": fn2 },
+ },
+ },
+ POST: {
+ api: {
+ settings: { "": fn3 },
+ },
+ },
+ },
+ };
+
+ assert.deepEqual(
+ findStaticHandler(table, "GET", [ "api", "home", "" ]),
+ { handlerFn: fn1, params: {} },
+ );
+ assert.deepEqual(
+ findStaticHandler(table, "PUT", [ "api", "home", "" ]),
+ null,
+ );
+
+ assert.deepEqual(
+ findStaticHandler(table, "GET", [ "api", "settings", "" ]),
+ { handlerFn: fn2, params: {} },
+ );
+ assert.deepEqual(
+ findStaticHandler(table, "POST", [ "api", "settings", "" ]),
+ { handlerFn: fn3, params: {} },
+ );
+ assert.deepEqual(
+ findStaticHandler(table, "PUT", [ "api", "settings", "" ]),
+ null,
+ );
+
+ assert.deepEqual(
+ findStaticHandler(table, "GET", [ "api", "profile", "" ]),
+ null,
+ );
+
+ assert.deepEqual(
+ findStaticHandler({}, "GET", [ "api", "profile", "" ]),
+ null,
+ );
+ });
+};
+
+const test_firstParamMatch = t => {
+ t.start("firstParamMatch()");
+
+ const params = {};
+ const fn1 = () => {};
+ const fn2 = () => {};
+
+ t.test("we BACKTRACK when searching down routes", () => {
+ const segments = [ "path", "split", "match", "" ];
+
+ const tree = {
+ path: {
+ split: {
+ MISMATCH: {
+ "": fn1,
+ },
+ },
+ ":param": {
+ match: {
+ "": fn2,
+ },
+ },
+ },
+ };
+
+ assert.deepEqual(
+ firstParamMatch(tree, segments, params),
+ { handlerFn: fn2, params: { param: "split" }},
+ );
+ });
+
+ t.test("ambiguous route prefers params at the end", () => {
+ const segments = [ "path", "param1", "param2", "" ];
+
+ const tree1 = {
+ path: {
+ ":shallower": {
+ param2: {
+ "": fn2,
+ },
+ },
+ },
+ };
+
+ const tree2 = assocIn(tree1, [ "path", "param1", ":deeper", "" ], fn1);
+
+ assert.deepEqual(
+ firstParamMatch(tree1, segments, params),
+ { handlerFn: fn2, params: { shallower: "param1" }},
+ );
+
+ assert.deepEqual(
+ firstParamMatch(tree2, segments, params),
+ { handlerFn: fn1, params: { deeper: "param2" }},
+ );
+ });
+
+ t.test("when 2 params are possible, we pick the first alphabetically", () => {
+ const segments = [ "user", "someId", "" ];
+
+ const tree1 = {
+ user: {
+ ":bbb": {
+ "": fn2,
+ },
+ },
+ };
+
+ const tree2 = assocIn(tree1, ["user", ":aaa", ""], fn1);
+
+ assert.deepEqual(
+ firstParamMatch(tree1, segments, params),
+ { handlerFn: fn2, params: { bbb: "someId" }},
+ );
+
+ assert.deepEqual(
+ firstParamMatch(tree2, segments, params),
+ { handlerFn: fn1, params: { aaa: "someId" }},
+ );
+ });
+};
+
+const test_findDynamicHandler = t => {
+ t.start("findDynamicHandler()");
+
+ t.test("daily usage cases", () => {
+ const fn1 = () => {};
+ const fn2 = () => {};
+
+ const table = {
+ dynamic: {
+ GET: {
+ users: {
+ "by-id": {
+ ":id": { "": fn1 },
+ },
+ },
+ },
+ PUT: {
+ user: {
+ ":user-id": {
+ info: { "": fn2 },
+ },
+ },
+ },
+ },
+ };
+
+ assert.deepEqual(
+ findDynamicHandler(table, "GET", [ "users", "by-id", "123", "" ]),
+ { handlerFn: fn1, params: { "id": "123" }},
+ );
+
+ assert.deepEqual(
+ findDynamicHandler({}, "GET", [ "users", "by-id", "123", "" ]),
+ null,
+ );
+
+ assert.deepEqual(
+ findDynamicHandler({ dynamic: { GET: { "": fn1 }}}, "GET", [ "users", "by-id", "123", "" ]),
+ null,
+ );
+
+ assert.deepEqual(
+ findDynamicHandler(table, "PUT", [ "user", "deadbeef", "info", "" ]),
+ { handlerFn: fn2, params: { "user-id": "deadbeef" }},
+ );
+ });
+};
+
+const test_findHandler = t => {
+ t.start("findHandler()");
+
+ t.test("mix of static and dynamic routes", () => {
+ const static1 = () => {};
+ const static2 = () => {};
+ const static3 = () => {};
+ const dynamic1 = () => {};
+ const dynamic2 = () => {};
+ const dynamic3 = () => {};
+ const dynamic4 = () => {};
+
+ const table = {
+ static: {
+ GET: {
+ user: {
+ "": static1,
+ },
+ pages: {
+ "": static2,
+ home: {
+ "": static3,
+ },
+ },
+ },
+ },
+ dynamic: {
+ GET: {
+ user: {
+ ":id": {
+ "": dynamic1,
+ },
+ },
+ },
+ PUT: {
+ user: {
+ ":id": {
+ "": dynamic2,
+ "info": {
+ "": dynamic3,
+ },
+ "preferences": {
+ "": dynamic4,
+ },
+ },
+ },
+ },
+ },
+ };
+
+ assert.deepEqual(
+ findHandler(table, "GET", "/"),
+ null,
+ );
+
+ assert.deepEqual(
+ findHandler(table, "GET", "/user/"),
+ { handlerFn: static1, params: {} },
+ );
+
+ assert.deepEqual(
+ findHandler(table, "GET", "/pages"),
+ { handlerFn: static2, params: {} },
+ );
+ assert.deepEqual(
+ findHandler(table, "GET", "/pages/home/"),
+ { handlerFn: static3, params: {} },
+ );
+
+ assert.deepEqual(
+ findHandler(table, "GET", "/user/some-id"),
+ { handlerFn: dynamic1, params: { id: "some-id" }},
+ );
+ assert.deepEqual(
+ findHandler(table, "GET", "/user/other-id/info"),
+ null,
+ );
+
+ assert.deepEqual(
+ findHandler(table, "PUT", "/user/other-id/info"),
+ { handlerFn: dynamic3, params: { id: "other-id" }},
+ );
+ assert.deepEqual(
+ findHandler(table, "PUT", "/user/another-id/preferences"),
+ { handlerFn: dynamic4, params: { id: "another-id" }},
+ );
+ assert.deepEqual(
+ findHandler(table, "POST", "/user/another-id/preferences"),
+ null,
+ );
+ });
+};
+
+const test_extractQueryParams = t => {
+ t.start("extractQueryParams()");
+
+ t.test("empty values", () => {
+ assert.deepEqual(extractQueryParams(), {});
+ assert.deepEqual(extractQueryParams(null), {});
+ assert.deepEqual(extractQueryParams(undefined), {});
+ });
+
+ t.test("we get a flat key-value strings", () => {
+ assert.deepEqual(
+ extractQueryParams("a[]=1&b=text&c=___"),
+ {
+ "a[]": "1",
+ b: "text",
+ c: "___",
+ },
+ );
+ });
+
+ t.test("duplicate values are suppressed, deterministically", () => {
+ assert.deepEqual(extractQueryParams("a=1&a=2&a=3"), { a: "3" });
+ assert.deepEqual(extractQueryParams("a=1&b=2&a=3"), { a: "3", b: "2" });
+ });
+};
+
+const test_handleRequest = t => {
+ t.start("handleRequest()");
+
+ const fn = req => req;
+
+ t.test("request without params", async () => {
+ const table = {
+ static: {
+ GET: {
+ "": fn,
+ },
+ },
+ };
+
+ assert.deepEqual(
+ await handleRequest(table, "GET", "/?q=1"),
+ {
+ params: {
+ path: {},
+ query: {
+ q: "1",
+ },
+ },
+ method: "GET",
+ path: "/",
+ handler: fn,
+ },
+ );
+ });
+
+ t.test("request with params", async () => {
+ const table = {
+ dynamic: {
+ PUT: {
+ api: {
+ user: {
+ ":userId": {
+ "": fn,
+ },
+ },
+ },
+ },
+ },
+ };
+
+ assert.deepEqual(
+ await handleRequest(table, "PUT", "/api/user/2222"),
+ {
+ params: {
+ path: {
+ userId: "2222",
+ },
+ query: {},
+ },
+ method: "PUT",
+ path: "/api/user/2222",
+ handler: fn,
+ },
+ );
+ });
+
+ t.test("missing route", async () => {
+ assert.deepEqual(
+ await handleRequest({}, "GET", "/"),
+ {
+ status: 404,
+ body: "Not Found",
+ },
+ );
+ });
+};
+
+const test_makeRequestListener = t => {
+ t.start("makeRequestListener()");
+
+ t.test("straightforward body execution", async () => {
+ const fn = _ => ({
+ status: "test status",
+ body: "test body",
+ headers: "test headers",
+ });
+ const routes = [[ "GET", "/route1", fn ]];
+ const requestListener = makeRequestListener(buildRoutes(routes));
+ const req = {
+ method: "GET",
+ url: "/route1",
+ };
+
+ const heads = [];
+ const bodies = [];
+ const res = {
+ writeHead: (status, headers) => heads.push({ status, headers }),
+ end: body => bodies.push(body),
+ };
+ await requestListener(req, res);
+
+ assert.deepEqual(
+ heads,
+ [{
+ status: "test status",
+ headers: "test headers",
+ }],
+ );
+ assert.deepEqual(
+ bodies,
+ [ "test body" ],
+ );
+ });
+
+ t.test("we break if handleRequest() throws an error", async () => {
+ const fn = _ => { throw new Error("handler error"); };
+ const routes = [[ "GET", "/route2", fn ]];
+ const requestListener = makeRequestListener(buildRoutes(routes));
+ const req = {
+ method: "GET",
+ url: "/route2",
+ };
+
+ const heads = [];
+ const bodies = [];
+ const res = {
+ writeHead: (status, headers) => heads.push({ status, headers }),
+ end: body => bodies.push(body),
+ };
+
+ await assert.rejects(
+ async () => await requestListener(req, res),
+ { message: "handler error" },
+ );
+
+ assert.deepEqual(heads, []);
+ assert.deepEqual(bodies, []);
+ });
+};
+
+const test_interceptorsFn = t => {
+ const next = x => x;
+
+ {
+ t.start("interceptorsFn().requestId()");
+
+ let i = 0;
+ const uuidFn = () => `${i++}`;
+
+ t.test("we add an id to whatever we receive", () => {
+ assert.deepEqual(
+ interceptorsFn({uuidFn}).requestId({}, next),
+ {
+ id: "0",
+ },
+ );
+ assert.deepEqual(
+ interceptorsFn({uuidFn}).requestId({ a: "existing data" }, next),
+ {
+ a: "existing data",
+ id: "1",
+ },
+ );
+ });
+
+ t.test(`we overwrite the "id" if it already exists`, async () => {
+ assert.deepEqual(
+ interceptorsFn({uuidFn}).requestId({ id: "before" }, next),
+ {
+ id: "2",
+ },
+ );
+ });
+ }
+
+ {
+ t.start("interceptorsFn().logged()");
+
+ t.test("we log before and after next()", async () => {
+ const contents = [];
+ const logger = x => contents.push(x);
+ const status = 201;
+ const req = {
+ id: "an ID",
+ url: "a URL",
+ method: "a method",
+ };
+
+ assert.deepEqual(
+ await interceptorsFn({logger}).logged(req, _ => ({ status })),
+ { status },
+ );
+ assert.deepEqual(
+ contents,
+ [
+ { ...req, type: "in-request" },
+ { id: req.id, status, type: "in-response" },
+ ],
+ );
+ });
+ }
+
+ {
+ t.start("interceptorsFn().contentType()");
+
+ t.test("empty values", async () => {
+ assert.deepEqual(
+ await interceptorsFn().contentType({}, next),
+ {
+ body: "",
+ headers: {
+ "Content-Type": "application/json",
+ "Content-Length": 0,
+ },
+ },
+ );
+
+ assert.deepEqual(
+ await interceptorsFn().contentType({ body: "" }, next),
+ {
+ body: "",
+ headers: {
+ "Content-Type": "text/html",
+ "Content-Length": 0,
+ },
+ },
+ );
+ });
+
+ t.test("body values", async () => {
+ assert.deepEqual(
+ await interceptorsFn().contentType({ body: { a: 1 }}, next),
+ {
+ body: `{"a":1}`,
+ headers: {
+ "Content-Type": "application/json",
+ "Content-Length": 7,
+ },
+ },
+ );
+
+ assert.deepEqual(
+ await interceptorsFn().contentType({ body: "<br />" }, next),
+ {
+ body: "<br />",
+ headers: {
+ "Content-Type": "text/html",
+ "Content-Length": 6,
+ },
+ },
+ );
+ });
+
+ t.test("header values preference", async () => {
+ assert.deepEqual(
+ await interceptorsFn().contentType({
+ body: "",
+ headers: {
+ "Content-Type": "we have preference",
+ "Content-Length": "and so do we",
+ },
+ }, next),
+ {
+ body: "",
+ headers: {
+ "Content-Type": "we have preference",
+ "Content-Length": "and so do we",
+ },
+ }
+ );
+ });
+
+ t.test("headers get propagated", async () => {
+ assert.deepEqual(
+ await interceptorsFn().contentType({
+ body: "",
+ headers: {
+ "My": "Header",
+ },
+ }, next),
+ {
+ body: "",
+ headers: {
+ "My": "Header",
+ "Content-Type": "text/html",
+ "Content-Length": 0,
+ },
+ },
+ );
+ });
+ }
+
+ {
+ t.start("interceptorsFn().serverError()");
+
+ t.test("no-op when no error occurs", async () => {
+ assert.deepEqual(
+ await interceptorsFn().serverError({ status: 1 }, next),
+ { status: 1 },
+ );
+ });
+
+ t.test(`an error is thrown if "status" is missing`, async () => {
+ const contents = [];
+ const logger = x => contents.push(x);
+ assert.deepEqual(
+ await interceptorsFn({ logger }).serverError({ id: 123 }, next),
+ {
+ status: 500,
+ body: "Internal Server Error",
+ },
+ );
+ assert.deepEqual(
+ contents,
+ [{
+ id: 123,
+ type: "server-error-interceptor",
+ message: `Missing "status"`,
+ }],
+ );
+ });
+
+ t.test("we turn a handler error into a 500 response", async () => {
+ const contents = [];
+ const logger = x => contents.push(x);
+ assert.deepEqual(
+ await interceptorsFn({ logger }).serverError(
+ { id: "some ID" },
+ _ => { throw new Error("My test error message"); },
+ ),
+ {
+ status: 500,
+ body: "Internal Server Error",
+ },
+ );
+ assert.deepEqual(
+ contents,
+ [{
+ id: "some ID",
+ type: "server-error-interceptor",
+ message: "My test error message",
+ }],
+ );
+ });
+ }
+};
+
+const test_chainInterceptors = t => {
+ t.start("chainInterceptors()");
+
+ t.test("empty values", () => {
+ assert.equal(chainInterceptors([])("req"), "req");
+ });
+
+ t.test("the order of interceptors matter", () => {
+ const a = [];
+
+ const i1 = (req, next) => {
+ a.push("i1");
+ return next(req);
+ };
+ const i2 = (req, next) => {
+ a.push("i2");
+ return next(req);
+ };
+
+ assert.equal(chainInterceptors([i1, i2])("req"), "req");
+ assert.deepEqual(a, ["i1", "i2"]);
+ assert.equal(chainInterceptors([i2, i1])("req"), "req");
+ assert.deepEqual(a, ["i1", "i2", "i2", "i1"]);
+ });
+
+ t.test("with ordering, interceptors implicitly depend on each other", () => {
+ const i1 = (req, next) => next({ ...req, id: 1 });
+ const i2 = (req, next) => next({ ...req, id: req.id + 1 });
+
+ assert.deepEqual(
+ chainInterceptors([i1, i2])({}),
+ { id: 2 },
+ );
+
+ assert.deepEqual(
+ chainInterceptors([i2])({}),
+ { id: NaN },
+ );
+
+ assert.deepEqual(
+ chainInterceptors([i2, i1])({}),
+ { id: 1 },
+ );
+ });
+
+ t.test("we can chain async interceptors", async () => {
+ const i1 = async (req, next) => await next({ ...req, i1: true });
+ const i2 = async (req, next) => await next({ ...req, i2: true });
+
+ assert.deepEqual(
+ await chainInterceptors([i1, i2])({}),
+ {
+ i1: true,
+ i2: true,
+ },
+ );
+ });
+};
+
+const test_wrapHandler = t => {
+ t.start("wrapHandler()");
+
+ t.test("a handler with chained interceptors change its behaviour", async () => {
+ let i = 0;
+ const uuidFn = () => `${i++}`;
+
+ const contents = [];
+ const logger = x => contents.push(x);
+
+ const interceptors = interceptorsFn({uuidFn, logger});
+
+ const fn = async _ => await { status: 1, body: { a: 1 }};
+ const wrappedFn = wrapHandler(fn, [
+ interceptors.requestId,
+ interceptors.logged,
+ interceptors.contentType,
+ ]);
+
+ const req = {
+ url: "URL",
+ method: "METHOD",
+ };
+
+ assert.deepEqual(
+ await fn(req),
+ { status: 1, body: { a: 1 }},
+ );
+ assert.deepEqual(contents, []);
+
+ assert.deepEqual(
+ await wrappedFn(req),
+ {
+ status: 1,
+ body: `{"a":1}`,
+ headers: {
+ "Content-Type": "application/json",
+ "Content-Length": 7,
+ },
+ },
+ );
+ assert.deepEqual(
+ contents,
+ [
+ {
+ id: "0",
+ url: "URL",
+ method: "METHOD",
+ type: "in-request",
+ },
+ {
+ id: "0",
+ status: 1,
+ type: "in-response",
+ },
+ ],
+ );
+ });
+};
+
+
+await runTests([
+ test_normalizeSegments,
+ test_pathToSegments,
+ test_hasPathParams,
+ test_addRoute,
+ test_buildRoutes,
+ test_findStaticHandler,
+ test_firstParamMatch,
+ test_findDynamicHandler,
+ test_findHandler,
+ test_extractQueryParams,
+ test_handleRequest,
+ test_makeRequestListener,
+ test_interceptorsFn,
+ test_chainInterceptors,
+ test_wrapHandler,
+]);