diff options
author | EuAndreh <eu@euandre.org> | 2024-03-08 07:47:47 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2024-03-08 07:47:47 -0300 |
commit | 1ad0f47d584f6783539217d8ca689de1a6b0f4da (patch) | |
tree | e703cda662aceed0800f904ef6c029ac17b9b6ba /tests/js | |
parent | src/hero.mjs: Make sure globalInceptors are used even when we get a 404 (diff) | |
download | papod-1ad0f47d584f6783539217d8ca689de1a6b0f4da.tar.gz papod-1ad0f47d584f6783539217d8ca689de1a6b0f4da.tar.xz |
src/web.mjs: Move logging and interceptors to the beginning of the file
Diffstat (limited to 'tests/js')
-rw-r--r-- | tests/js/hero.mjs | 982 |
1 files changed, 491 insertions, 491 deletions
diff --git a/tests/js/hero.mjs b/tests/js/hero.mjs index a8883d5..9a5cf25 100644 --- a/tests/js/hero.mjs +++ b/tests/js/hero.mjs @@ -7,6 +7,16 @@ import procees from "node:process"; import * as runner from "../runner.mjs"; import * as u from "../../src/utils.mjs"; import { + loggerDefaults, + loggerGlobals, + configLogger, + logit, + makeLogger, + interceptorsFn, + interceptors, + defaultInterceptors, + chainInterceptors, + wrapHandler, normalizeSegments, pathToSegments, hasPathParams, @@ -20,16 +30,6 @@ import { make404Handler, handleRequest, makeRequestListener, - loggerDefaults, - loggerGlobals, - configLogger, - logit, - makeLogger, - interceptorsFn, - interceptors, - defaultInterceptors, - chainInterceptors, - wrapHandler, actionsFn, lineHandlerFn, rmIf, @@ -44,6 +44,481 @@ import { } from "../../src/hero.mjs"; +const test_configLogger = async t => { + t.start("configLogger()"); + + await t.test("globals starts empty", () => { + assert.deepEqual(loggerGlobals, {}); + }); + + await t.test("is gets becomes we assign it", () => { + const globals = { + app: "my-app", + color: "green", + version: "deadbeef", + }; + configLogger(globals); + assert.deepEqual(loggerGlobals, globals); + }); + + await t.test("we can reset it", () => { + configLogger({}); + assert.deepEqual(loggerGlobals, {}); + }); +}; + +const test_logit = async t => { + t.start("logit()"); + + await t.test("we can log data", () => { + configLogger({ app: "hero-based app" }); + const contents = []; + const writerFn = x => contents.push(x); + + logit(writerFn, "my level", { a: 1, type: "log-test" }); + assert.deepEqual(contents.map(JSON.parse), [{ + ...loggerDefaults, + app: "hero-based app", + level: "my level", + a: 1, + type: "log-test", + }]); + + /// reset the logger out of the values specific to this test + configLogger({}); + }); + + await t.test("the object can change previous fallback values", () => { + const contents = []; + const writerFn = x => contents.push(x); + + configLogger({ + level: "unseen", + a: "unseen", + }); + logit(writerFn, "overwritten by level", { a: "overwritten by o" }); + + configLogger({ + pid: "overwritten by loggerGlobals", + }); + logit(writerFn, "unseen", { level: "overwritten by o" }); + + /// reset the logger out of the values specific to this test + configLogger({}); + + assert.deepEqual(contents.map(JSON.parse), [ + { + ...loggerDefaults, + pid: process.pid, + level: "overwritten by level", + a: "overwritten by o", + }, + { + ...loggerDefaults, + pid: "overwritten by loggerGlobals", + level: "overwritten by o", + }, + ]); + + }); + + await t.test("we can't log unserializable things", () => { + const obj = { self: null }; + obj.self = obj; + assert.throws(() => logit(obj), TypeError); + }); +}; + +const test_makeLogger = async t => { + t.start("makeLogger()"); + + await t.test("various log levels", () => { + const contents = []; + const writerFn = x => contents.push(x); + const log = makeLogger(writerFn); + + log.info ({ type: "expected" }); + log.warn ({ type: "worrysome" }); + log.error({ type: "bad" }); + assert.deepEqual(contents.map(JSON.parse), [ + { + ...loggerDefaults, + level: "INFO", + type: "expected", + }, + { + ...loggerDefaults, + level: "WARN", + type: "worrysome", + }, + { + ...loggerDefaults, + level: "ERROR", + type: "bad", + }, + ]); + }); + + await t.test("debug only works when $DEBUG is set", () => { + const contents = []; + const writerFn = x => contents.push(x); + const log = makeLogger(writerFn); + + const previous = process.env.DEBUG; + delete process.env.DEBUG; + + log.debug({ x: "ignored" }); + process.env.DEBUG = "1"; + log.debug({ x: "seen" }); + delete process.env.DEBUG; + /// call the function that toggles + log.debug({ x: "ignored" }); + + assert.deepEqual(contents.map(JSON.parse), [{ + ...loggerDefaults, + level: "DEBUG", + x: "seen", + }]); + + process.env.DEBUG = previous; + }); +}; + +const test_interceptorsFn = async t => { + const next = x => x; + + { + t.start("interceptorsFn().requestId()"); + + let i = 0; + const uuidFn = () => `${i++}`; + + await 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", + }, + ); + }); + + await 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()"); + + await t.test("we log before and after next()", async () => { + const contents = []; + const logger = { info: 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()"); + + await 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, + }, + }, + ); + }); + + await 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, + }, + }, + ); + }); + + await 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", + }, + }, + ); + }); + + await 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()"); + + await t.test("no-op when no error occurs", async () => { + assert.deepEqual( + await interceptorsFn().serverError({ status: 1 }, next), + { status: 1 }, + ); + }); + + await t.test(`an error is thrown if "status" is missing`, async () => { + const contents = []; + const logger = { error: x => contents.push(x) }; + assert.deepEqual( + await interceptorsFn({ logger }).serverError({ id: 123 }, next), + { + status: 500, + body: "Internal Server Error\n", + }, + ); + assert.deepEqual( + contents, + [{ + id: 123, + type: "server-error-interceptor", + message: `Missing "status"`, + }], + ); + }); + + await t.test("we turn a handler error into a 500 response", async () => { + const contents = []; + const logger = { error: 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\n", + }, + ); + assert.deepEqual( + contents, + [{ + id: "some ID", + type: "server-error-interceptor", + message: "My test error message", + }], + ); + }); + }; +}; + +const test_chainInterceptors = async t => { + t.start("chainInterceptors()"); + + await t.test("empty values", () => { + assert.equal(chainInterceptors([])("req"), "req"); + }); + + await 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"]); + }); + + await 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 }, + ); + }); + + await 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 = async t => { + t.start("wrapHandler()"); + + await t.test("noop when arr is empty", () => { + const fn = () => {}; + const wrappedFn1 = wrapHandler(fn, []); + assert.deepEqual(fn, wrappedFn1); + + const wrappedFn2 = wrapHandler(fn, [ interceptors.requestId ]); + assert.notDeepEqual(fn, wrappedFn2); + }); + + await t.test("a handler with chained interceptors change its behaviour", async () => { + let i = 0; + const uuidFn = () => `${i++}`; + + const contents = []; + const logger = { info: 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", + }, + ], + ); + }); +}; + const test_normalizeSegments = async t => { t.start("normalizeSegments()"); @@ -661,481 +1136,6 @@ const test_makeRequestListener = async t => { }); }; -const test_configLogger = async t => { - t.start("configLogger()"); - - await t.test("globals starts empty", () => { - assert.deepEqual(loggerGlobals, {}); - }); - - await t.test("is gets becomes we assign it", () => { - const globals = { - app: "my-app", - color: "green", - version: "deadbeef", - }; - configLogger(globals); - assert.deepEqual(loggerGlobals, globals); - }); - - await t.test("we can reset it", () => { - configLogger({}); - assert.deepEqual(loggerGlobals, {}); - }); -}; - -const test_logit = async t => { - t.start("logit()"); - - await t.test("we can log data", () => { - configLogger({ app: "hero-based app" }); - const contents = []; - const writerFn = x => contents.push(x); - - logit(writerFn, "my level", { a: 1, type: "log-test" }); - assert.deepEqual(contents.map(JSON.parse), [{ - ...loggerDefaults, - app: "hero-based app", - level: "my level", - a: 1, - type: "log-test", - }]); - - /// reset the logger out of the values specific to this test - configLogger({}); - }); - - await t.test("the object can change previous fallback values", () => { - const contents = []; - const writerFn = x => contents.push(x); - - configLogger({ - level: "unseen", - a: "unseen", - }); - logit(writerFn, "overwritten by level", { a: "overwritten by o" }); - - configLogger({ - pid: "overwritten by loggerGlobals", - }); - logit(writerFn, "unseen", { level: "overwritten by o" }); - - /// reset the logger out of the values specific to this test - configLogger({}); - - assert.deepEqual(contents.map(JSON.parse), [ - { - ...loggerDefaults, - pid: process.pid, - level: "overwritten by level", - a: "overwritten by o", - }, - { - ...loggerDefaults, - pid: "overwritten by loggerGlobals", - level: "overwritten by o", - }, - ]); - - }); - - await t.test("we can't log unserializable things", () => { - const obj = { self: null }; - obj.self = obj; - assert.throws(() => logit(obj), TypeError); - }); -}; - -const test_makeLogger = async t => { - t.start("makeLogger()"); - - await t.test("various log levels", () => { - const contents = []; - const writerFn = x => contents.push(x); - const log = makeLogger(writerFn); - - log.info ({ type: "expected" }); - log.warn ({ type: "worrysome" }); - log.error({ type: "bad" }); - assert.deepEqual(contents.map(JSON.parse), [ - { - ...loggerDefaults, - level: "INFO", - type: "expected", - }, - { - ...loggerDefaults, - level: "WARN", - type: "worrysome", - }, - { - ...loggerDefaults, - level: "ERROR", - type: "bad", - }, - ]); - }); - - await t.test("debug only works when $DEBUG is set", () => { - const contents = []; - const writerFn = x => contents.push(x); - const log = makeLogger(writerFn); - - const previous = process.env.DEBUG; - delete process.env.DEBUG; - - log.debug({ x: "ignored" }); - process.env.DEBUG = "1"; - log.debug({ x: "seen" }); - delete process.env.DEBUG; - /// call the function that toggles - log.debug({ x: "ignored" }); - - assert.deepEqual(contents.map(JSON.parse), [{ - ...loggerDefaults, - level: "DEBUG", - x: "seen", - }]); - - process.env.DEBUG = previous; - }); -}; - -const test_interceptorsFn = async t => { - const next = x => x; - - { - t.start("interceptorsFn().requestId()"); - - let i = 0; - const uuidFn = () => `${i++}`; - - await 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", - }, - ); - }); - - await 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()"); - - await t.test("we log before and after next()", async () => { - const contents = []; - const logger = { info: 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()"); - - await 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, - }, - }, - ); - }); - - await 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, - }, - }, - ); - }); - - await 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", - }, - }, - ); - }); - - await 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()"); - - await t.test("no-op when no error occurs", async () => { - assert.deepEqual( - await interceptorsFn().serverError({ status: 1 }, next), - { status: 1 }, - ); - }); - - await t.test(`an error is thrown if "status" is missing`, async () => { - const contents = []; - const logger = { error: x => contents.push(x) }; - assert.deepEqual( - await interceptorsFn({ logger }).serverError({ id: 123 }, next), - { - status: 500, - body: "Internal Server Error\n", - }, - ); - assert.deepEqual( - contents, - [{ - id: 123, - type: "server-error-interceptor", - message: `Missing "status"`, - }], - ); - }); - - await t.test("we turn a handler error into a 500 response", async () => { - const contents = []; - const logger = { error: 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\n", - }, - ); - assert.deepEqual( - contents, - [{ - id: "some ID", - type: "server-error-interceptor", - message: "My test error message", - }], - ); - }); - }; -}; - -const test_chainInterceptors = async t => { - t.start("chainInterceptors()"); - - await t.test("empty values", () => { - assert.equal(chainInterceptors([])("req"), "req"); - }); - - await 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"]); - }); - - await 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 }, - ); - }); - - await 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 = async t => { - t.start("wrapHandler()"); - - await t.test("noop when arr is empty", () => { - const fn = () => {}; - const wrappedFn1 = wrapHandler(fn, []); - assert.deepEqual(fn, wrappedFn1); - - const wrappedFn2 = wrapHandler(fn, [ interceptors.requestId ]); - assert.notDeepEqual(fn, wrappedFn2); - }); - - await t.test("a handler with chained interceptors change its behaviour", async () => { - let i = 0; - const uuidFn = () => `${i++}`; - - const contents = []; - const logger = { info: 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", - }, - ], - ); - }); -}; - const test_actionsFn = async t => { { t.start(`actionsFn()["toggle-debug-env"()]`); @@ -1734,6 +1734,12 @@ const test_buildServer = async t => { await runner.runTests([ + test_configLogger, + test_logit, + test_makeLogger, + test_interceptorsFn, + test_chainInterceptors, + test_wrapHandler, test_normalizeSegments, test_pathToSegments, test_hasPathParams, @@ -1746,12 +1752,6 @@ await runner.runTests([ test_make404Handler, test_handleRequest, test_makeRequestListener, - test_configLogger, - test_logit, - test_makeLogger, - test_interceptorsFn, - test_chainInterceptors, - test_wrapHandler, test_actionsFn, test_lineHandlerFn, test_buildRoutes, |