diff options
author | EuAndreh <eu@euandre.org> | 2024-03-21 09:43:43 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2024-03-21 09:45:43 -0300 |
commit | e7e60205bda309c8aaecc36a03f6c3ad0ec84cd2 (patch) | |
tree | 555dd577672eaabb6c85b8a013865a252f9e5a62 /tests | |
parent | tests/rand.c: s/Taken/Derived/ (diff) | |
download | papod-e7e60205bda309c8aaecc36a03f6c3ad0ec84cd2.tar.gz papod-e7e60205bda309c8aaecc36a03f6c3ad0ec84cd2.tar.xz |
src/hero.mjs: Retire code
Diffstat (limited to 'tests')
-rw-r--r-- | tests/js/hero.mjs | 2430 |
1 files changed, 0 insertions, 2430 deletions
diff --git a/tests/js/hero.mjs b/tests/js/hero.mjs deleted file mode 100644 index 6390ad0..0000000 --- a/tests/js/hero.mjs +++ /dev/null @@ -1,2430 +0,0 @@ -import assert from "node:assert/strict"; -import fs from "node:fs"; -import http from "node:http"; -import path from "node:path"; -import process from "node:process"; - -import * as runner from "../runner.mjs"; -import * as u from "../../src/utils.mjs"; -import { - loggerDefaults, - loggerGlobals, - configLogger, - logit, - now, - makeLogger, - statusMessage, - statusResponse, - isValidMethod, - isValidUpgrade, - isValidKey, - isValidVersion, - validateUpgrade, - computeHash, - interceptorsFn, - interceptors, - defaultInterceptors, - chainInterceptors, - wrapHandler, - normalizeSegments, - pathToSegments, - hasPathParams, - isValidLabel, - comboForLabel, - addRoute, - findStaticHandler, - firstParamMatch, - findDynamicHandler, - findHandler, - extractQueryParams, - renderStatus, - renderHeaders, - buildHeader, - writeHead, - make404Handler, - handleRequest, - makeRequestListener, - makeUpgradeListener, - actionsFn, - lineHandlerFn, - rmIf, - mkfifo, - makeLineEmitter, - makeReopeningPipeReader, - makePipeReaderFn, - buildRoutes, - buildTable, - promisifyServer, - buildServer, -} 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_now = async t => { - t.start("now()"); - - await t.test("we get an ISO date", () => { - const s = now(); - assert.deepEqual(s, new Date(s).toISOString()); - }); -}; - -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); - let i = 0; - const timestampFn = () => `${i++}`; - - logit(writerFn, timestampFn, "my level", { a: 1, type: "log-test" }); - assert.deepEqual(contents.map(JSON.parse), [{ - ...loggerDefaults, - app: "hero-based app", - level: "my level", - timestamp: "0", - 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); - let i = 0; - const timestampFn = () => `${i++}`; - - configLogger({ - level: "unseen", - a: "unseen", - }); - logit(writerFn, timestampFn, "overwritten by level", { a: "overwritten by o" }); - - configLogger({ - pid: "overwritten by loggerGlobals", - }); - logit(writerFn, timestampFn, "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", - timestamp: "0", - a: "overwritten by o", - }, - { - ...loggerDefaults, - pid: "overwritten by loggerGlobals", - level: "overwritten by o", - timestamp: "1", - }, - ]); - - }); - - 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); - let i = 0; - const timestampFn = () => `${i++}`; - const log = makeLogger({ writerFn, timestampFn }); - - log.info ({ type: "expected" }); - log.warn ({ type: "worrysome" }); - log.error({ type: "bad" }); - assert.deepEqual(contents.map(JSON.parse), [ - { - ...loggerDefaults, - level: "INFO", - timestamp: "0", - type: "expected", - }, - { - ...loggerDefaults, - level: "WARN", - timestamp: "1", - type: "expected", - type: "worrysome", - }, - { - ...loggerDefaults, - level: "ERROR", - timestamp: "2", - type: "expected", - type: "bad", - }, - ]); - }); - - await t.test("debug only works when $DEBUG is set", () => { - const contents = []; - const writerFn = x => contents.push(x); - let i = 0; - const timestampFn = () => `${i++}`; - const log = makeLogger({ writerFn, timestampFn }); - - 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", - timestamp: "0", - x: "seen", - }]); - - process.env.DEBUG = previous; - }); -}; - -const test_statusMessage = async t => { - t.start("statusMessage()"); - - await t.test("we get the expected values", () => { - assert.deepEqual( - [ 101, 200, 409, 422, 502, 503 ].map(statusMessage), - [ - "Switching Protocols\n", - "OK\n", - "Conflict\n", - "Unprocessable Entity\n", - "Bad Gateway\n", - "Service Unavailable\n", - ], - ); - }); -}; - -const test_statusResponse = async t => { - t.start("statusResponse()"); - - await t.test("we get a returnable body", () => { - assert.deepEqual(statusResponse(202), { - status: 202, - body: "Accepted\n", - }); - }); -}; - -const test_isValidMethod = async t => { - t.start("isValidMethod()"); - - await t.test("we only accept a single value", () => { - assert.ok(isValidMethod("GET")); - assert.ok(!isValidMethod("get")); - assert.ok(!isValidMethod("PUT")); - }) -}; - -const test_isValidUpgrade = async t => { - t.start("isValidUpgrade()"); - - await t.test("we ignore the case", () => { - assert.ok(isValidUpgrade("Websocket")); - assert.ok(isValidUpgrade("WebSocket")); - assert.ok(isValidUpgrade("websocket")); - assert.ok(!isValidUpgrade("web socket")); - }); -}; - -const test_isValidKey = async t => { - t.start("isValidKey()"); - - await t.test("RFC example value", () => { - const key = "dGhlIHNhbXBsZSBub25jZQ=="; - assert.ok(isValidKey(key)); - }); - - await t.test("wrong example values", () => { - const key1 = "_GhlIHNhbXBsZSBub25jZQ=="; - const key2 = "dGhlIHNhbXBsZSBub25jZQ="; - assert.ok(!isValidKey(key1)); - assert.ok(!isValidKey(key2)); - }); -}; - -const test_isValidVersion = async t => { - t.start("isValidVersion()"); - - await t.test("we only accept a single value", () => { - assert.ok(isValidVersion(13)); - assert.ok(!isValidVersion(9)); - assert.ok(!isValidVersion(10)); - assert.ok(!isValidVersion(11)); - assert.ok(!isValidVersion(12)); - }); -}; - -const test_validateUpgrade = async t => { - t.start("validateUpgrade()"); - - await t.test("invalid method", () => { - assert.deepEqual(validateUpgrade("POST", {}), { - isValid: false, - response: { - status: 405, - body: "Method Not Allowed\n", - }, - }); - }); - - await t.test("missing upgrade", () => { - assert.deepEqual(validateUpgrade("GET", {}), { - isValid: false, - response: { - status: 400, - body: 'Missing "Upgrade" header\n', - }, - }); - }); - - await t.test("invalid upgrade", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "web socket", - }), { - isValid: false, - response: { - status: 400, - body: 'Invalid "Upgrade" value\n', - }, - }); - }); - - await t.test("missing sec-websocket-key", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "websocket", - }), { - isValid: false, - response: { - status: 400, - body: 'Missing "Sec-WebSocket-Key" header\n', - }, - }); - }); - - await t.test("invalid sec-websocket-key", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "websocket", - "sec-websocket-key": "bad value", - }), { - isValid: false, - response: { - status: 400, - body: 'Invalid "Sec-WebSocket-Key" value\n', - }, - }); - }); - - await t.test("missing sec-websocket-version", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "websocket", - "sec-websocket-key": "aaaaabbbbbcccccdddddee==", - }), { - isValid: false, - response: { - status: 400, - body: 'Missing "Sec-WebSocket-Version" header\n', - }, - }); - }); - - await t.test("invalid sec-websocket-version", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "websocket", - "sec-websocket-key": "aaaaabbbbbcccccdddddee==", - "sec-websocket-version": "12", - }), { - isValid: false, - response: { - status: 400, - body: 'Invalid "Sec-WebSocket-Version" value\n', - }, - }); - }); - - await t.test("valid upgrade", () => { - assert.deepEqual(validateUpgrade("GET", { - "upgrade": "websocket", - "sec-websocket-key": "aaaaabbbbbcccccdddddee==", - "sec-websocket-version": "13", - }), { - isValid: true, - }); - }); -}; - -const test_computeHash = async t => { - t.start("computeHash()"); - - await t.test("RFC example value", () => { - const key = "dGhlIHNhbXBsZSBub25jZQ=="; - const hash = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; - assert.equal(computeHash(key), hash); - }); - - await t.test("a key used in other tests", () => { - const key = "aaaaabbbbbcccccdddddee=="; - const hash = "eHnDP9gUz224y002aFCe7swigxg="; - assert.equal(computeHash(key), hash); - }); -}; - -const test_interceptorsFn = async t => { - const next = x => ({ ...x, nextCalled: true }); - - { - 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", - nextCalled: true, - }, - ); - assert.deepEqual( - interceptorsFn({uuidFn}).requestId({ a: "existing data" }, next), - { - a: "existing data", - id: "1", - nextCalled: true, - }, - ); - }); - - await t.test(`we overwrite the "id" if it already exists`, async () => { - assert.deepEqual( - interceptorsFn({uuidFn}).requestId({ id: "before" }, next), - { - id: "2", - nextCalled: true, - }, - ); - }); - }; - - { - 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", - upgrade: true, - }; - - assert.deepEqual( - await interceptorsFn({logger}).logged(req, _ => ({ status })), - { status }, - ); - assert.deepEqual( - contents.map(o => u.dissoc(o, "timings")), - [ - { ...req, type: "in-request" }, - { id: req.id, status, type: "in-response" }, - ], - ); - assert.equal(typeof contents[1].timings.ms.before, "number"); - assert.equal(typeof contents[1].timings.ms.after, "number"); - assert.equal(typeof contents[1].timings.ms.duration, "number"); - }); - }; - - { - t.start("interceptorsFn().contentType()"); - - await t.test("empty values", async () => { - await assert.rejects( - async () => await interceptorsFn().contentType({}, next), - assert.AssertionError, - ); - assert.deepEqual( - await interceptorsFn().contentType({ - status: 202, - }, next), - { - status: 202, - body: "Accepted\n", - headers: { - "Content-Type": "text/plain", - "Content-Length": 9, - }, - nextCalled: true, - }, - ); - - assert.deepEqual( - await interceptorsFn().contentType({ - status: 200, - body: "", - }, next), - { - status: 200, - body: "", - headers: { - "Content-Type": "text/html", - "Content-Length": 0, - }, - nextCalled: true, - }, - ); - }); - - await t.test("body values", async () => { - assert.deepEqual( - await interceptorsFn().contentType({ - status: 201, - body: { a: 1 }, - }, next), - { - status: 201, - body: `{"a":1}`, - headers: { - "Content-Type": "application/json", - "Content-Length": 7, - }, - nextCalled: true, - }, - ); - - assert.deepEqual( - await interceptorsFn().contentType({ - status: 200, - body: "<br />", - }, next), - { - status: 200, - body: "<br />", - headers: { - "Content-Type": "text/html", - "Content-Length": 6, - }, - nextCalled: true, - }, - ); - }); - - await t.test("header values preference", async () => { - assert.deepEqual( - await interceptorsFn().contentType({ - status: 503, - body: "", - headers: { - "Content-Type": "we have preference", - "Content-Length": "and so do we", - }, - }, next), - { - status: 503, - body: "", - headers: { - "Content-Type": "we have preference", - "Content-Length": "and so do we", - }, - nextCalled: true, - }, - ); - }); - - await t.test("headers get propagated", async () => { - assert.deepEqual( - await interceptorsFn().contentType({ - status: 500, - body: "", - headers: { - "My": "Header", - }, - }, next), - { - status: 500, - body: "", - headers: { - "My": "Header", - "Content-Type": "text/html", - "Content-Length": 0, - }, - nextCalled: true, - }, - ); - }); - }; - - { - t.start("interceptorsFn().serverError()"); - - await t.test("no-op when no error occurs", async () => { - assert.deepEqual( - await interceptorsFn().serverError({ status: 1 }, next), - { - status: 1, - nextCalled: true, - }, - ); - }); - - 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.map(o => ({ ...o, stacktrace: typeof o.stacktrace })), - [{ - id: 123, - type: "server-error-interceptor", - message: `Missing "status"`, - stacktrace: "string", - }], - ); - }); - - 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.map(o => ({ ...o, stacktrace: typeof o.stacktrace })), - [{ - id: "some ID", - type: "server-error-interceptor", - message: "My test error message", - stacktrace: "string", - }], - ); - }); - }; - - { - t.start("interceptorsFn().websocketHandshake()"); - await t.test("no-op when not an upgrade request", async () => { - assert.deepEqual( - await interceptorsFn().websocketHandshake({ - upgrade: false, - }, next), - { - upgrade: false, - nextCalled: true, - }, - ); - }); - - await t.test("when invalid we forward what validateUpgrade() says", async () => { - assert.deepEqual( - await interceptorsFn().websocketHandshake({ - upgrade: true, - method: "GET", - headers: {}, - }, next), - { - status: 400, - body: 'Missing "Upgrade" header\n', - }, - ); - }); - - await t.test("otherwise we upgrade the connection", async () => { - assert.deepEqual( - await interceptorsFn().websocketHandshake({ - upgrade: true, - method: "GET", - headers: { - "upgrade": "websocket", - "sec-websocket-key": "aaaaabbbbbcccccdddddee==", - "sec-websocket-version": "13", - }, - }, next), - { - status: 101, - headers: { - "Connection": "Upgrade", - "Upgrade": "websocket", - "Sec-WebSocket-Accept": "eHnDP9gUz224y002aFCe7swigxg=", - }, - }, - ); - }); - }; -}; - -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", - upgrade: false, - }; - - 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.map(o => u.dissoc(o, "timings")), - [ - { - id: "0", - url: "URL", - method: "METHOD", - type: "in-request", - upgrade: false, - }, - { - id: "0", - status: 1, - type: "in-response", - }, - ], - ); - }); -}; - -const test_normalizeSegments = async t => { - t.start("normalizeSegments()"); - - await t.test("unchanged when already normalized", () => { - assert.deepEqual(normalizeSegments([""]), [""]); - }); - - await 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 = async t => { - t.start("pathToSegments()"); - - await 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", ""]); - }); - - await 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 = async t => { - t.start("hasPathParam()"); - - await t.test("has it", () => { - assert(hasPathParams(["some", ":path", ""])); - assert(hasPathParams(["path", ":params", ""])); - assert(hasPathParams(["api", "user", ":id", "info", ""])); - }); - - await 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_isValidLabel = async t => { - t.start("isValidLabel()"); - - await t.test("typo examples", () => { - assert.ok(!isValidLabel("get")); - assert.ok(!isValidLabel("WebSocket")); - assert.ok(!isValidLabel("WEBSOCKETS")); - assert.ok(!isValidLabel("ws")); - }); - - await t.test("valid usages", () => { - assert.ok(isValidLabel("GET")); - assert.ok(isValidLabel("PUT")); - assert.ok(isValidLabel("WEBSOCKET")); - }); -}; - -const test_comboForLabel = async t => { - t.start("comboForLabel()"); - - await t.test("websocket gets its own combo", () => { - assert.deepEqual( - comboForLabel("WEBSOCKET", "IGNORED"), - [ "websocket", "GET" ], - ); - }); - - await t.test("otherwise we get what pass", () => { - assert.deepEqual( - comboForLabel("not-websocket", "a-keyword"), - [ "a-keyword", "not-websocket" ], - ); - }); -}; - -const test_addRoute = async t => { - t.start("addRoute()"); - - const fn1 = () => {}; - - await 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({}, "WEBSOCKET", "/socket", fn1), - { websocket: { GET: { socket: { "": 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 }}}}}}, - }, - }, - ); - }); - - await t.test("bad method", () => { - assert.throws( - () => addRoute({}, "VERB", "/path", fn1), - assert.AssertionError, - ); - }); - - await t.test("empty methods array", () => { - assert.deepEqual(addRoute({}, [], "", fn1), {}); - }); - - await 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_findStaticHandler = async t => { - t.start("findStaticHandler()"); - - await t.test("multiple accesses to the same table", () => { - const fn1 = () => {}; - const fn2 = () => {}; - const fn3 = () => {}; - const fn4 = () => {}; - - const table = { - static: { - GET: { - api: { - home: { "": fn1 }, - settings: { "": fn2 }, - }, - }, - POST: { - api: { - settings: { "": fn3 }, - }, - }, - }, - websocket: { - GET: { - api: { - socket: { "": fn4 }, - }, - }, - } - }; - - assert.deepEqual( - findStaticHandler(table, "GET", [ "api", "home", "" ], "static"), - { handlerFn: fn1, params: {} }, - ); - assert.deepEqual( - findStaticHandler(table, "PUT", [ "api", "home", "" ], "static"), - null, - ); - - assert.deepEqual( - findStaticHandler(table, "GET", [ "api", "settings", "" ], "static"), - { handlerFn: fn2, params: {} }, - ); - assert.deepEqual( - findStaticHandler(table, "POST", [ "api", "settings", "" ], "static"), - { handlerFn: fn3, params: {} }, - ); - assert.deepEqual( - findStaticHandler(table, "PUT", [ "api", "settings", "" ], "static"), - null, - ); - - assert.deepEqual( - findStaticHandler(table, "GET", [ "api", "profile", "" ], "static"), - null, - ); - - assert.deepEqual( - findStaticHandler({}, "GET", [ "api", "profile", "" ], "static"), - null, - ); - - assert.deepEqual( - findStaticHandler(table, "GET", [ "api", "socket", "" ], "static"), - null, - ); - - assert.deepEqual( - findStaticHandler(table, "GET", [ "api", "socket", "" ], "websocket"), - { handlerFn: fn4, params: {} }, - ); - }); -}; - -const test_firstParamMatch = async t => { - t.start("firstParamMatch()"); - - const params = {}; - const fn1 = () => {}; - const fn2 = () => {}; - - await 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" }}, - ); - }); - - await t.test("ambiguous route prefers params at the end", () => { - const segments = [ "path", "param1", "param2", "" ]; - - const tree1 = { - path: { - ":shallower": { - param2: { - "": fn2, - }, - }, - }, - }; - - const tree2 = u.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" }}, - ); - }); - - await t.test("when 2 params are possible, we pick the first alphabetically", () => { - const segments = [ "user", "someId", "" ]; - - const tree1 = { - user: { - ":bbb": { - "": fn2, - }, - }, - }; - - const tree2 = u.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" }}, - ); - }); - - await t.test(`we deal with segments that start with ":"`, () => { - const segments = [ "path", ":param", "" ]; - const tree = { - path: { - ":arg": { - "": fn1, - }, - }, - }; - - assert.deepEqual( - firstParamMatch(tree, segments, params), - { handlerFn: fn1, params: { arg: ":param" }}, - ); - }); -}; - -const test_findDynamicHandler = async t => { - t.start("findDynamicHandler()"); - - await 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 = async t => { - t.start("findHandler()"); - - await t.test("mix of static, dynamic and websocket routes", () => { - const static1 = () => {}; - const static2 = () => {}; - const static3 = () => {}; - const dynamic1 = () => {}; - const dynamic2 = () => {}; - const dynamic3 = () => {}; - const dynamic4 = () => {}; - const websocket1 = () => {}; - const websocket2 = () => {}; - - const table = { - static: { - GET: { - user: { - "": static1, - }, - pages: { - "": static2, - home: { - "": static3, - }, - }, - }, - }, - dynamic: { - GET: { - user: { - ":id": { - "": dynamic1, - }, - }, - }, - PUT: { - user: { - ":id": { - "": dynamic2, - "info": { - "": dynamic3, - }, - "preferences": { - "": dynamic4, - }, - }, - }, - }, - }, - websocket: { - GET: { - user: { - "": websocket1, - socket: { - "": websocket2, - }, - }, - }, - }, - }; - - assert.deepEqual( - findHandler(table, "GET", "/", false), - null, - ); - - assert.deepEqual( - findHandler(table, "GET", "/user/", false), - { handlerFn: static1, params: {} }, - ); - assert.deepEqual( - findHandler(table, "GET", "/user/", true), - { handlerFn: websocket1, params: {} }, - ); - - assert.deepEqual( - findHandler(table, "GET", "/pages", false), - { handlerFn: static2, params: {} }, - ); - assert.deepEqual( - findHandler(table, "GET", "/pages/home/", false), - { handlerFn: static3, params: {} }, - ); - - assert.deepEqual( - findHandler(table, "GET", "/user/some-id", false), - { handlerFn: dynamic1, params: { id: "some-id" }}, - ); - assert.deepEqual( - findHandler(table, "GET", "/user/other-id/info", false), - null, - ); - - assert.deepEqual( - findHandler(table, "PUT", "/user/other-id/info", false), - { handlerFn: dynamic3, params: { id: "other-id" }}, - ); - assert.deepEqual( - findHandler(table, "PUT", "/user/another-id/preferences", false), - { handlerFn: dynamic4, params: { id: "another-id" }}, - ); - assert.deepEqual( - findHandler(table, "POST", "/user/another-id/preferences", false), - null, - ); - - assert.deepEqual( - findHandler(table, "GET", "/user/socket", true), - { handlerFn: websocket2, params: {} }, - ); - }); -}; - -const test_extractQueryParams = async t => { - t.start("extractQueryParams()"); - - await t.test("empty values", () => { - assert.deepEqual(extractQueryParams(), {}); - assert.deepEqual(extractQueryParams(null), {}); - assert.deepEqual(extractQueryParams(undefined), {}); - }); - - await t.test("we get a flat key-value strings", () => { - assert.deepEqual( - extractQueryParams("a[]=1&b=text&c=___"), - { - "a[]": "1", - b: "text", - c: "___", - }, - ); - }); - - await 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_renderStatus = async t => { - t.start("renderStatus()"); - - await t.test("good statuses", () => { - assert.equal(renderStatus(101), "HTTP/1.1 101 Switching Protocols"); - assert.equal(renderStatus(202), "HTTP/1.1 202 Accepted"); - assert.equal(renderStatus(409), "HTTP/1.1 409 Conflict"); - }); -}; - -const test_renderHeaders = async t => { - t.start("renderHeaders()"); - - await t.test("empty values", () => { - assert.deepEqual(renderHeaders({}), []); - assert.deepEqual(renderHeaders(), []); - }) - - await t.test("values are rendered and sorted", () => { - assert.deepEqual(renderHeaders({ a: "one", Z: "two" }), [ - "a: one", - "Z: two", - ]); - }); - - await t.test("GIGO for newlines", () => { - assert.deepEqual(renderHeaders({ a: "\nx: 1\r\n", }), [ - "a: \nx: 1\r\n", - ]); - }); -}; - -const test_buildHeader = async t => { - t.start("buildHeader()"); - - await t.test("empty values", () => { - assert.equal( - buildHeader(200, {}), - "HTTP/1.1 200 OK\r\n" + - "\r\n", - ); - }); - - await t.test("we compose the status line and the headers", () => { - assert.equal( - buildHeader(201, { a: "1", b: "2" }), - "HTTP/1.1 201 Created\r\n" + - "a: 1\r\n" + - "b: 2\r\n" + - "\r\n", - ); - }); -}; - -const test_writeHead = async t => { - t.start("writeHead()"); - - await t.test("we simply write what buildHeader() gives us", () => { - const contents = []; - const socket = { write: x => contents.push(x) }; - writeHead(socket, 202, { "Key": "Value" }); - assert.deepEqual(contents, [ - "HTTP/1.1 202 Accepted\r\n" + - "Key: Value\r\n" + - "\r\n", - ]); - }); -}; - -const test_make404Handler = async t => { - t.start("make404Handler"); - - await t.test("empty interceptors", () => { - assert.deepEqual( - new Set(Object.keys(make404Handler([]))), - new Set(["handlerFn", "params"]), - ); - assert.deepEqual(make404Handler([]).params, {}); - assert.deepEqual(make404Handler([]).handlerFn(Math.random()), { - status: 404, - body: "Not Found\n", - }); - }); -}; - -const test_handleRequest = async t => { - t.start("handleRequest()"); - - const fn = req => req; - - await t.test("request without params", async () => { - const table = { - static: { - GET: { - "": fn, - }, - }, - }; - const req = { - method: "GET", - url: "/?q=1", - headers: { - a: "1", - b: "two", - }, - upgrade: false, - socket: null, - }; - - assert.deepEqual( - await handleRequest(table, req), - { - params: { - path: {}, - query: { - q: "1", - }, - }, - method: "GET", - path: "/", - headers: { - a: "1", - b: "two", - }, - ref: req, - handler: fn, - upgrade: false, - socket: null, - }, - ); - }); - - await t.test("request with params", async () => { - const table = { - dynamic: { - PUT: { - api: { - user: { - ":userId": { - "": fn, - }, - }, - }, - }, - }, - interceptors: [], - }; - const req = { - method: "PUT", - url: "/api/user/2222", - headers: { - h1: "H1", - h2: "h2", - }, - upgrade: false, - socket: null, - }; - - assert.deepEqual( - await handleRequest(table, req), - { - params: { - path: { - userId: "2222", - }, - query: {}, - }, - method: "PUT", - path: "/api/user/2222", - headers: { - h1: "H1", - h2: "h2", - }, - handler: fn, - ref: req, - upgrade: false, - socket: null, - }, - ); - }); - - await t.test("upgrade request", async () => { - const socket = () => {}; - const handler = req => { - assert.equal(req.socket, socket); - return "handler ret"; - }; - const table = { - websocket: { - GET: { - api: { - socket: { - "": handler, - }, - }, - }, - }, - interceptors: [], - }; - const req = { - method: "GET", - url: "/api/socket", - upgrade: true, - socket, - }; - - assert.deepEqual( - await handleRequest(table, req), - "handler ret", - ); - }); - - await t.test("missing route", async () => { - assert.deepEqual( - await handleRequest({ interceptors: [] }, { - method: "GET", - url: "/", - }), - { - status: 404, - body: "Not Found\n", - }, - ); - }); -}; - -const test_makeRequestListener = async t => { - t.start("makeRequestListener()"); - - await 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" ], - ); - }); - - await 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_makeUpgradeListener = async t => { - t.start("makeUpgradeListener()"); - - await t.test("straightforward connection stablishing", async () => { - const calls = []; - const fn = x => calls.push([x.upgrade, x.socket]); - const routes = [[ "WEBSOCKET", "/socket", fn ]]; - const table = buildRoutes(routes, [ - interceptors.contentType, - interceptors.websocketHandshake, - ]); - const upgradeListener = makeUpgradeListener(table); - - const req = { - method: "GET", - url: "/socket", - upgrade: true, - headers: { - "upgrade": "websocket", - "sec-websocket-key": "aaaaabbbbbcccccdddddee==", - "sec-websocket-version": "13", - }, - }; - - const contents = []; - const socket = { - end: () => assert.ok(false), - write: x => contents.push(x), - }; - await upgradeListener(req, socket); - - assert.deepEqual(calls, [[true, socket]]); - assert.deepEqual(contents, [ - "HTTP/1.1 101 Switching Protocols\r\n" + - "Connection: Upgrade\r\n" + - "Content-Length: 20\r\n" + - "Content-Type: text/plain\r\n" + - "Sec-WebSocket-Accept: eHnDP9gUz224y002aFCe7swigxg=\r\n" + - "Upgrade: websocket\r\n" + - "\r\n", - "Switching Protocols\n", - ]); - }); - - await t.test("early termination cases", async () => { - const routes = [[ "WEBSOCKET", "/a-socket", null ]]; - const table = buildRoutes(routes, [ - interceptors.websocketHandshake, - ]); - const upgradeListener = makeUpgradeListener(table); - - const req = { - method: "GET", - url: "/a-socket", - upgrade: true, - headers: { - "upgrade": "websocket", - }, - }; - - let ended = false; - const contents = []; - const socket = { - end: () => ended = true, - write: x => contents.push(x), - }; - - await upgradeListener(req, socket); - - assert.ok(ended); - assert.deepEqual(contents, [ - "HTTP/1.1 400 Bad Request\r\n\r\n", - 'Missing "Sec-WebSocket-Key" header\n', - ]); - }); -}; - -const test_actionsFn = async t => { - { - t.start(`actionsFn()["toggle-debug-env"()]`); - - await t.test("we can toggle back and forth", () => { - const contents = []; - const logger = { info: x => contents.push(x) }; - const actions = actionsFn({logger}); - - const previous = process.env.DEBUG; - delete process.env.DEBUG; - - actions["toggle-debug-env"]("action-text-1"); - assert.equal(process.env.DEBUG, "1"); - actions["toggle-debug-env"]("action-text-2"); - assert.equal(process.env.DEBUG, undefined); - - assert.deepEqual(contents, [ - { - type: "action-response", - action: "action-text-1", - message: "toggle process.env.DEBUG", - before: null, - after: "1", - }, - { - type: "action-response", - action: "action-text-2", - message: "toggle process.env.DEBUG", - before: "1", - after: null, - }, - ]); - - process.env.DEBUG = previous; - }); - }; - - { - t.start(`actionsFn()["config-dump"]()`); - - await t.test("we just dump data as a log entry", () => { - const contents = []; - const logger = { info: x => contents.push(x) }; - const actions = actionsFn({logger}); - - configLogger({}); - actions["config-dump"]("first-call"); - configLogger({ some: "thing", }); - actions["config-dump"]("second-call"); - configLogger({}); - - assert.deepEqual(contents, [ - { - type: "action-response", - action: "first-call", - data: { - pid: process.pid, - ppid: process.ppid, - tool: "hero", - }, - }, - { - type: "action-response", - action: "second-call", - data: { - pid: process.pid, - ppid: process.ppid, - tool: "hero", - some: "thing", - }, - }, - - ]); - }); - }; - - { - t.start(`actionsFn()["ping"]()`); - - await t.test("simple pinging", () => { - const contents = []; - const logger = { info: x => contents.push(x) }; - const actions = actionsFn({ logger }); - - configLogger({}); - actions["ping"]("blah"); - actions["ping"](null); - - assert.deepEqual(contents, [ - { message: "pong" }, - { message: "pong" }, - ]); - }); - }; -}; - -const test_lineHandlerFn = async t => { - t.start("lineHandlerFn()"); - - await t.test("empty values", () => { - const contents = []; - const logger = { info: x => contents.push(x) }; - const lineHandler = lineHandlerFn({logger, actionsMap: {}}); - - lineHandler(""); - lineHandler("{}"); - lineHandler(`{"action": "this-action-does-not-exist"}`); - - assert.deepEqual(contents.map(x => x.type), [ - "invalid-cmd-input", - "missing-key-action", - "unsupported-action", - ]); - }); - - await t.test("calling an action", () => { - const contents = []; - const logger = { info: x => contents.push(x) }; - const lineHandler = lineHandlerFn({ logger: null, actionsMap: { - "an-action": (arg1, arg2, arg3) => [arg1, arg2, arg3], - }}); - - const ret1 = lineHandler(`{"action": "an-action"}`); - const ret2 = lineHandler(`{"action": "an-action", "args": [1, "text", 2]}`); - assert.deepEqual(ret1, ["an-action", undefined, undefined]); - assert.deepEqual(ret2, ["an-action", 1, "text"]); - }); -}; - -const test_rmIf = async t => { - t.start("rmIf()"); - - const path = "tests/hero-0.txt"; - - await t.test("rm when exists", async () => { - fs.writeFileSync(path, " ", { flush: true }); - assert.ok(fs.existsSync(path)); - rmIf(path); - assert.ok(!fs.existsSync(path)); - }); - - await t.test("noop otherwise", async () => { - assert.ok(!fs.existsSync(path)); - rmIf(path); - assert.ok(!fs.existsSync(path)); - }); -}; - -const test_mkfifo = async t => { - t.start("mkfifo()"); - - await t.test("invalid paths", () => { - assert.throws( - () => mkfifo("tests/this/dir/does/not/exist/file.fifo"), - { status: 1 }, - ); - assert.throws( - () => mkfifo(""), - { status: 1 }, - ); - }); - - await t.test("error when path already exists", async () => { - const path = "tests/hero-mkfifo-0.pipe" - - fs.writeFileSync(path, " ", { flush: true }); - - const stats = fs.statSync(path); - assert.ok(!stats.isFIFO()); - - assert.throws( - () => mkfifo(path), - { status: 1 }, - ); - }); - - await t.test("new pipe file", async () => { - const path = "tests/hero-mkfifo-1.pipe" - - rmIf(path); - assert.ok(!fs.existsSync(path)); - mkfifo(path); - assert.ok(fs.existsSync(path)); - - const stats = fs.statSync(path); - assert.ok(stats.isFIFO()); - }); -}; - -const test_makeLineEmitter = async t => { - t.start("makeLineEmitter()"); - - await t.test("noop when we only get empty strings", async () => { - const entries = []; - const record = x => entries.push(x); - const emitter = makeLineEmitter(record); - - emitter(""); - emitter(""); - emitter(""); - emitter(""); - emitter(""); - - assert.deepEqual(entries, []); - }); - - await t.test("empty strings when we only get newlines", async () => { - const entries = []; - const record = x => entries.push(x); - const emitter = makeLineEmitter(record); - - emitter("\n\n\n"); - emitter("\n\n"); - emitter("\n"); - - assert.deepEqual(entries, [ "", "", "", "", "", "" ]); - }); - - await t.test("noop also if we never get a newline", async () => { - const entries = []; - const record = x => entries.push(x); - const emitter = makeLineEmitter(record); - - emitter(" "); - emitter("some string"); - emitter(" "); - emitter("a lot of text"); - assert.deepEqual(entries, []); - - emitter("\n"); - assert.deepEqual(entries, [ " some string a lot of text" ]); - }); - - await t.test("if a newline always comes, we always emit", async () => { - const entries = []; - const record = x => entries.push(x); - const emitter = makeLineEmitter(record); - - emitter("first\n"); - emitter("second\n"); - emitter("third\n"); - - assert.deepEqual(entries, [ "first", "second", "third" ]); - }); - - await t.test("lines can acummulate accross multiple writes", async () => { - const entries = []; - const record = x => entries.push(x); - const emitter = makeLineEmitter(record); - - emitter("fir"); - emitter("s"); - emitter("t\ns"); - emitter("econd\nthir"); - emitter("d"); - emitter("\n"); - emitter("fourth\nfifth\nsixth"); - - assert.deepEqual(entries, [ - "first", - "second", - "third", - "fourth", - "fifth", - ]); - }); -}; - -const test_makeReopeningPipeReader = async t => { - t.start("makeReopeningPipeReader()"); - - await t.test("we can init to not reopen from the start", async () => { - const path = "tests/hero-makeReopeningPipeReader-0.pipe"; - const shouldReopenPipe = { ref: false }; - const lines = [] - const logs = []; - const lineFn = x => lines.push(x); - const logger = { debug: x => logs.push(x) }; - - const previous = process.env.DEBUG; - delete process.env.DEBUG; - - rmIf(path); - mkfifo(path); - const pipe = {}; - makeReopeningPipeReader( - shouldReopenPipe, - path, - { lineFn, logger }, - pipe, - ); - - return new Promise((resolve, reject) => { - fs.createWriteStream(path).end().close(); - pipe.ref.on("close", () => { - assert.deepEqual(lines, []); - assert.deepEqual(logs, [{ - message: "pipe closed, NOT reopening", - }]); - - process.env.DEBUG = previous; - resolve(); - }); - }); - }); - - await t.test("we can reopen more than once", async () => { - const path = "tests/hero-makeReopeningPipeReader-1.pipe"; - const shouldReopenPipe = { ref: true }; - const lines = []; - const logs = []; - const lineFn = x => lines.push(x); - const logger = { debug: x => logs.push(x) }; - - const previous = process.env.DEBUG; - delete process.env.DEBUG; - - rmIf(path); - mkfifo(path); - const pipe = {}; - makeReopeningPipeReader( - shouldReopenPipe, - path, - { lineFn, logger }, - pipe, - ); - return new Promise((resolve, reject) => { - fs.createWriteStream(path).end("first\n").close(); - pipe.ref.on("close", () => { - fs.createWriteStream(path).end("second\n").close(); - pipe.ref.on("close", () => { - shouldReopenPipe.ref = false; - fs.createWriteStream(path).end("third\n").close(); - pipe.ref.on("close", () => { - assert.deepEqual(lines, [ - "first", - "second", - "third", - ]); - assert.deepEqual(logs, [ - { message: "pipe closed, reopening" }, - { message: "pipe closed, reopening" }, - { message: "pipe closed, NOT reopening" }, - ]); - process.env.DEBUG = previous; - resolve(); - }); - }); - }); - }); - }); -}; - -const test_makePipeReaderFn = async t => { - t.start("makePipeReaderFn()"); - - await t.test("we can close it directly on creation with no data", async () => { - const path = "tests/hero-makePipeReader-0.pipe"; - const lines = []; - const logs = []; - const lineFn = x => lines.push(x); - const logger = { debug: x => logs.push(x) }; - const makePipeReader = makePipeReaderFn({ lineFn, logger }); - - rmIf(path); - await makePipeReader(path)(); - - assert.deepEqual(lines, []); - assert.deepEqual(logs, [{ message: "pipe closed, NOT reopening" }]); - }); -}; - -const test_buildRoutes = async t => { - t.start("buildRoutes()"); - - await t.test("empty values", () => { - assert.deepEqual(buildRoutes([]), {}); - }); - - await 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), - ); - }); - - await t.test("wrapped handler functions", async () => { - const handler = req => ({ ...req, handled: true }); - const interceptor = (req, next) => next({ ...req, intercepted: true }); - - const routes = [ - [ "GET", "/without", handler ], - [ "GET", "/with", handler, [ interceptor ] ], - ]; - - const table = buildRoutes(routes); - - { - const { handled, intercepted } = - await handleRequest(table, { - method: "GET", - url: "/without", - }); - assert.deepEqual( - { handled, intercepted }, - { handled: true, intercepted: undefined }, - ); - }; - { - const { handled, intercepted } = - await handleRequest(table, { - method: "GET", - url: "/with", - }); - assert.deepEqual( - { handled, intercepted }, - { handled: true, intercepted: true }, - ); - }; - }); - - await t.test("interceptors are combined", async () => { - const handler = req => ({ ...req, handled: true }); - const interceptor1 = (req, next) => next({ ...req, interceptor1: true }); - const interceptor2 = (req, next) => next({ ...req, interceptor2: true }); - - const routes = [ - [ "GET", "/global-only", handler ], - [ "GET", "/global-and-local", handler, [ interceptor2 ] ], - ]; - - const table = buildRoutes(routes, [ interceptor1 ]); - - { - const { handled, interceptor1, interceptor2 } = - await handleRequest(table, { - method: "GET", - url: "/global-only", - }); - assert.deepEqual( - { handled, interceptor1, interceptor2 }, - { handled: true, interceptor1: true, interceptor2: undefined }, - ); - }; - { - const { handled, interceptor1, interceptor2 } = - await handleRequest(table, { - method: "GET", - url: "/global-and-local", - }); - assert.deepEqual( - { handled, interceptor1, interceptor2 }, - { handled: true, interceptor1: true, interceptor2: true }, - ); - }; - }); - - await 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_buildTable = async t => { - t.start("buildTable()"); - - await t.test('we just add the "interceptors" key to what buildRoutes() gives us', () => { - assert.deepEqual( - buildTable([], [ "i1", "i2" ]), - { interceptors: [ "i1", "i2" ] }, - ); - }); -}; - -const test_promisifyServer = async t => { - t.start("promisifyServer()"); - - await t.test("we can access the underlying server ref", () => { - const server = promisifyServer("app-name", http.createServer(() => {})); - assert.ok(server.ref instanceof http.Server); - }); -}; - -const test_buildServer = async t => { - t.start("buildServer()"); - - const socketRequest = (socketPath, path, headers = {}) => - new Promise((resolve, reject) => { - const callback = res => { - let body = ""; - res.on("data", chunk => body += chunk); - res.on("error", reject); - res.on("end", () => resolve({ - body, - status: res.statusCode, - })); - }; - const request = http.request({ - socketPath, - path, - headers, - }, callback); - request.end(); - }); - - await t.test("empty values", async () => { - const socket = "tests/hero-buildServer-0.socket"; - const pipe = "tests/hero-buildServer-0.pipe"; - const name = "my-empty-app"; - const server = buildServer({ name, socket, pipe }); - await server.start(); - const response = await socketRequest(socket, "/anything"); - await server.stop(); - - assert.deepEqual(response, { status: 404, body: "Not Found\n" }); - assert.deepEqual(server.info(), { name, socket, pipe }); - }); - - await t.test("default values", () => { - const name = path.basename(process.cwd()); - assert.deepEqual(buildServer().info(), { - name, - socket: `${name}.socket`, - pipe: `${name}.pipe`, - }); - }); - - await t.test("integrated application server", async () => { - const socket = "tests/hero-buildServer-1.socket"; - const pipe = "tests/hero-buildServer-1.pipe"; - const name = "the-app"; - const pathHandler = req => ({ status: 200, body: "something" }); - const routes = [ [ "GET", "/path", pathHandler ] ]; - const server = buildServer({ name, routes, socket, pipe }); - - await server.start(); - const response = await socketRequest(socket, "/path"); - await server.stop(); - - assert.deepEqual(response, { status: 200, body: "something" }); - }); -}; - - -await runner.runTests([ - test_configLogger, - test_logit, - test_now, - test_makeLogger, - test_statusMessage, - test_statusResponse, - test_isValidMethod, - test_isValidUpgrade, - test_isValidKey, - test_isValidVersion, - test_validateUpgrade, - test_computeHash, - test_interceptorsFn, - test_chainInterceptors, - test_wrapHandler, - test_normalizeSegments, - test_pathToSegments, - test_hasPathParams, - test_isValidLabel, - test_comboForLabel, - test_addRoute, - test_findStaticHandler, - test_firstParamMatch, - test_findDynamicHandler, - test_findHandler, - test_extractQueryParams, - test_renderStatus, - test_renderHeaders, - test_buildHeader, - test_writeHead, - test_make404Handler, - test_handleRequest, - test_makeRequestListener, - test_makeUpgradeListener, - test_actionsFn, - test_lineHandlerFn, - test_rmIf, - test_mkfifo, - test_makeLineEmitter, - test_makeReopeningPipeReader, - test_makePipeReaderFn, - test_buildRoutes, - test_buildTable, - test_promisifyServer, - test_buildServer, -]); |