UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

493 lines (438 loc) 17.3 kB
import * as glob from "glob"; import * as path from "path"; import { config, utils } from "./../../src/index"; describe("Utils", () => { describe("util.sleep", () => { test("it sleeps", async () => { const start = new Date().getTime(); await utils.sleep(100); const end = new Date().getTime(); expect(end - start).toBeGreaterThanOrEqual(99); expect(end - start).toBeLessThan(200); }); }); describe("utils.arrayUnique", () => { test("works", () => { const a = [1, 2, 3, 3, 4, 4, 4, 5, 5, 5]; expect(utils.arrayUnique(a)).toEqual([1, 2, 3, 4, 5]); }); }); describe("utils.collapseObjectToArray", () => { test("fails with numerical keys", () => { const o = { 0: "a", 1: "b" }; const response = utils.collapseObjectToArray(o); expect(response).toEqual(["a", "b"]); }); test("fails with non-numerical keys", () => { const o = { a: 1 }; const response = utils.collapseObjectToArray(o); expect(response).toEqual(false); }); }); describe("utils.hashMerge", () => { const A = { a: 1, b: 2 }; const B = { b: -2, c: 3 }; const C = { a: 1, b: { m: 10, n: 11 } }; const D = { a: 1, b: { n: 111, o: 22, p: {} } }; const E = { b: {} }; const N = { b: null } as Record<string, any>; const U = { b: undefined } as Record<string, any>; test("simple", () => { const Z = utils.hashMerge(A, B); expect(Z.a).toEqual(1); expect(Z.b).toEqual(-2); expect(Z.c).toEqual(3); }); test("directional", () => { const Z = utils.hashMerge(B, A); expect(Z.a).toEqual(1); expect(Z.b).toEqual(2); expect(Z.c).toEqual(3); }); test("nested", () => { const Z = utils.hashMerge(C, D); expect(Z.a).toEqual(1); expect(Z.b.m).toEqual(10); expect(Z.b.n).toEqual(111); expect(Z.b.o).toEqual(22); expect(Z.b.p).toEqual({}); }); test("empty01", () => { const Z = utils.hashMerge(E, D); expect(Z.a).toEqual(1); expect(Z.b.n).toEqual(111); expect(Z.b.o).toEqual(22); expect(Z.b.p).toEqual({}); }); test("empty10", () => { const Z = utils.hashMerge(D, E); expect(Z.a).toEqual(1); expect(Z.b.n).toEqual(111); expect(Z.b.o).toEqual(22); expect(Z.b.p).toEqual({}); }); test("chained", () => { const Z = utils.hashMerge(utils.hashMerge(C, E), D); expect(Z.a).toEqual(1); expect(Z.b.m).toEqual(10); expect(Z.b.n).toEqual(111); expect(Z.b.o).toEqual(22); expect(Z.b.p).toEqual({}); }); test("null", () => { const Z = utils.hashMerge(A, N); expect(Z.a).toEqual(1); expect(Z.b).toBeUndefined(); }); test("undefined", () => { const Z = utils.hashMerge(A, U); expect(Z.a).toEqual(1); expect(Z.b).toEqual(2); }); }); describe("eventLoopDelay", () => { test("works", async () => { const delay = await utils.eventLoopDelay(10000); expect(delay).toBeGreaterThan(0); expect(delay).toBeLessThan(1); }); }); describe("#parseHeadersForClientAddress", () => { test("only x-real-ip, port is null", () => { const headers = { "x-real-ip": "10.11.12.13", }; const { ip, port } = utils.parseHeadersForClientAddress(headers); expect(ip).toEqual("10.11.12.13"); expect(port).toBeFalsy(); }); test("load balancer, x-forwarded-for format", () => { const headers = { "x-forwarded-for": "35.36.37.38", "x-forwarded-port": "80", }; const { ip, port } = utils.parseHeadersForClientAddress(headers); expect(ip).toEqual("35.36.37.38"); expect(port).toEqual("80"); }); }); describe("#parseIPv6URI", () => { test("address and port", () => { const uri = "[2604:4480::5]:8080"; const parts = utils.parseIPv6URI(uri); expect(parts.host).toEqual("2604:4480::5"); expect(parts.port).toEqual(8080); }); test("address without port", () => { const uri = "2604:4480::5"; const parts = utils.parseIPv6URI(uri); expect(parts.host).toEqual("2604:4480::5"); expect(parts.port).toEqual(80); }); test("full uri", () => { const uri = "http://[2604:4480::5]:8080/foo/bar"; const parts = utils.parseIPv6URI(uri); expect(parts.host).toEqual("2604:4480::5"); expect(parts.port).toEqual(8080); }); test("failing address", () => { const uri = "[2604:4480:z:5]:80"; try { const parts = utils.parseIPv6URI(uri); console.log(parts); } catch (e) { expect(e.message).toEqual("failed to parse address"); } }); test("should parse locally scoped ipv6 URIs without port", () => { const uri = "fe80::1ff:fe23:4567:890a%eth2"; const parts = utils.parseIPv6URI(uri); expect(parts.host).toEqual("fe80::1ff:fe23:4567:890a%eth2"); expect(parts.port).toEqual(80); }); test("should parse locally scoped ipv6 URIs with port", () => { const uri = "[fe80::1ff:fe23:4567:890a%eth2]:8080"; const parts = utils.parseIPv6URI(uri); expect(parts.host).toEqual("fe80::1ff:fe23:4567:890a%eth2"); expect(parts.port).toEqual(8080); }); }); describe("utils.arrayStartingMatch", () => { test("finds matching arrays", () => { const a = [1, 2, 3]; const b = [1, 2, 3, 4, 5]; const numberResult = utils.arrayStartingMatch(a, b); expect(numberResult).toBe(true); const c = ["a", "b", "c"]; const d = ["a", "b", "c", "d", "e"]; const stringResult = utils.arrayStartingMatch(c, d); expect(stringResult).toBe(true); }); test("finds non-matching arrays", () => { const a = [1, 3]; const b = [1, 2, 3, 4, 5]; const numberResult = utils.arrayStartingMatch(a, b); expect(numberResult).toBe(false); const c = ["a", "b", "c"]; const d = ["a", "b", "d", "e"]; const stringResult = utils.arrayStartingMatch(c, d); expect(stringResult).toBe(false); }); test("does not pass with empty arrays; first", () => { const a: number[] = []; const b = [1, 2, 3, 4, 5]; const result = utils.arrayStartingMatch(a, b); expect(result).toBe(false); }); test("does not pass with empty arrays; second", () => { const a = [1, 2, 3, 4, 5]; const b: number[] = []; const result = utils.arrayStartingMatch(a, b); expect(result).toBe(false); }); }); describe("utils.replaceDistWithSrc", () => { test("it replaces paths from dist to src", () => { const p = `${config.general!.paths.action[0]}/new-actions/test.ts`; const withDist = utils.replaceDistWithSrc(p); expect(withDist).toMatch("/src/actions/new-actions/test.ts"); }); }); describe("utils.filterObjectForLogging", () => { beforeEach(() => { config.logger!.maxLogArrayLength = 100; expect(config.general!.filteredParams.length).toEqual(0); }); afterEach(() => { // after each test, empty the array config.general!.filteredParams = []; config.logger!.maxLogArrayLength = 10; }); const testInput = { p1: 1, p2: "s3cr3t", o1: { o1p1: 1, o1p2: "also-s3cr3t", o2: { o2p1: "this is ok", o2p2: "extremely-s3cr3t", }, }, o2: { name: "same as o1`s inner object!", o2p1: "nothing secret", }, a1: ["a", "b", "c"], a2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], }; test("can filter top level params, no matter the type", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredParams as string[]).push("p1", "p2", "o2"); const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.p1).toEqual("[FILTERED]"); expect(filteredParams.p2).toEqual("[FILTERED]"); expect(filteredParams.o2).toEqual("[FILTERED]"); // entire object filtered expect(filteredParams.o1).toEqual(testInput.o1); // unchanged expect(filteredParams.a1).toEqual(testInput.a1); // unchanged expect(filteredParams.a2).toEqual(testInput.a2); // unchanged }); test("will not filter things that do not exist", () => { // Identity const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams).toEqual(testInput); (config.general!.filteredParams as string[]).push( "p3", "p4", "o1.o3", "o1.o2.p1", ); const filteredParams2 = utils.filterObjectForLogging(inputs); expect(filteredParams2).toEqual(testInput); expect(filteredParams.a1).toEqual(testInput.a1); // unchanged expect(filteredParams.a2).toEqual(testInput.a2); // unchanged }); test("can filter a single level dot notation", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredParams as string[]).push( "p1", "o1.o1p1", "somethingNotExist", ); const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.p1).toEqual("[FILTERED]"); expect(filteredParams.o1.o1p1).toEqual("[FILTERED]"); // Unchanged things expect(filteredParams.p2).toEqual(testInput.p2); expect(filteredParams.o1.o1p2).toEqual(testInput.o1.o1p2); expect(filteredParams.o1.o2).toEqual(testInput.o1.o2); expect(filteredParams.o2).toEqual(testInput.o2); expect(filteredParams.a1).toEqual(testInput.a1); expect(filteredParams.a2).toEqual(testInput.a2); }); test("can filter two levels deep", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredParams as string[]).push( "p2", "o1.o2.o2p1", "o1.o2.notThere", ); const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.p2).toEqual("[FILTERED]"); expect(filteredParams.o1.o2.o2p1).toEqual("[FILTERED]"); // Unchanged things expect(filteredParams.p1).toEqual(testInput.p1); expect(filteredParams.o1.o1p1).toEqual(testInput.o1.o1p1); expect(filteredParams.o1.o2.o2p2).toEqual(testInput.o1.o2.o2p2); expect(filteredParams.a1).toEqual(testInput.a1); expect(filteredParams.a2).toEqual(testInput.a2); }); test("can filter with a function rather than an array", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone config.general!.filteredParams = () => { return ["p1", "p2", "o2"]; }; const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.p1).toEqual("[FILTERED]"); expect(filteredParams.p2).toEqual("[FILTERED]"); expect(filteredParams.o2).toEqual("[FILTERED]"); // entire object filtered // Unchanged things expect(filteredParams.o1).toEqual(testInput.o1); expect(filteredParams.a1).toEqual(testInput.a1); expect(filteredParams.a2).toEqual(testInput.a2); }); test("short arrays will be displayed as-is", () => { config.logger!.maxLogArrayLength = 100; const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.a1).toEqual(testInput.a1); expect(filteredParams.a2).toEqual(testInput.a2); }); test("long arrays will be collected", () => { config.logger!.maxLogArrayLength = 10; const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone const filteredParams = utils.filterObjectForLogging(inputs); expect(filteredParams.a1).toEqual(testInput.a1); expect(filteredParams.a2).toEqual("11 items"); }); }); describe("utils.filterResponseForLogging", () => { beforeEach(() => { config.logger!.maxLogArrayLength = 100; expect(config.general!.filteredResponse.length).toEqual(0); }); afterEach(() => { // after each test, empty the array config.general!.filteredResponse = []; config.logger!.maxLogArrayLength = 10; }); const testInput = { p1: 1, p2: "s3cr3t", o1: { o1p1: 1, o1p2: "also-s3cr3t", o2: { o2p1: "this is ok", o2p2: "extremely-s3cr3t", }, }, o2: { name: "same as o1`s inner object!", o2p1: "nothing secret", }, a1: ["a", "b", "c"], a2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], }; test("can filter top level params, no matter the type", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredResponse as string[]).push("p1", "p2", "o2"); const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse.p1).toEqual("[FILTERED]"); expect(filteredResponse.p2).toEqual("[FILTERED]"); expect(filteredResponse.o2).toEqual("[FILTERED]"); // entire object filtered expect(filteredResponse.o1).toEqual(testInput.o1); // unchanged expect(filteredResponse.a1).toEqual(testInput.a1); // unchanged expect(filteredResponse.a2).toEqual(testInput.a2); // unchanged }); test("will not filter things that do not exist", () => { // Identity const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse).toEqual(testInput); (config.general!.filteredResponse as string[]).push( "p3", "p4", "o1.o3", "o1.o2.p1", ); const filteredResponse2 = utils.filterResponseForLogging(inputs); expect(filteredResponse2).toEqual(testInput); expect(filteredResponse2.a1).toEqual(testInput.a1); // unchanged expect(filteredResponse2.a2).toEqual(testInput.a2); // unchanged }); test("can filter a single level dot notation", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredResponse as string[]).push( "p1", "o1.o1p1", "somethingNotExist", ); const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse.p1).toEqual("[FILTERED]"); expect(filteredResponse.o1.o1p1).toEqual("[FILTERED]"); // Unchanged things expect(filteredResponse.p2).toEqual(testInput.p2); expect(filteredResponse.o1.o1p2).toEqual(testInput.o1.o1p2); expect(filteredResponse.o1.o2).toEqual(testInput.o1.o2); expect(filteredResponse.o2).toEqual(testInput.o2); expect(filteredResponse.a1).toEqual(testInput.a1); expect(filteredResponse.a2).toEqual(testInput.a2); }); test("can filter two levels deep", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone (config.general!.filteredResponse as string[]).push( "p2", "o1.o2.o2p1", "o1.o2.notThere", ); const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse.p2).toEqual("[FILTERED]"); expect(filteredResponse.o1.o2.o2p1).toEqual("[FILTERED]"); // Unchanged things expect(filteredResponse.p1).toEqual(testInput.p1); expect(filteredResponse.o1.o1p1).toEqual(testInput.o1.o1p1); expect(filteredResponse.o1.o2.o2p2).toEqual(testInput.o1.o2.o2p2); expect(filteredResponse.a1).toEqual(testInput.a1); expect(filteredResponse.a2).toEqual(testInput.a2); }); test("can filter with a function rather than an array", () => { const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone config.general!.filteredResponse = () => { return ["p1", "p2", "o2"]; }; const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse.p1).toEqual("[FILTERED]"); expect(filteredResponse.p2).toEqual("[FILTERED]"); expect(filteredResponse.o2).toEqual("[FILTERED]"); // entire object filtered expect(filteredResponse.o1).toEqual(testInput.o1); // unchanged expect(filteredResponse.a1).toEqual(testInput.a1); expect(filteredResponse.a2).toEqual(testInput.a2); }); test("long arrays will be collected", () => { config.logger!.maxLogArrayLength = 10; const inputs = JSON.parse(JSON.stringify(testInput)); // quick deep Clone const filteredResponse = utils.filterResponseForLogging(inputs); expect(filteredResponse.a1).toEqual(testInput.a1); expect(filteredResponse.a2).toEqual("11 items"); }); test("safeGlobSync to match normal glob.sync", () => { const directory = __dirname; // if it is windows platform includes backslash (\\) const pattern = "/**/*.ts"; const normalResult = glob.sync(directory.replace(/\\/g, "/") + pattern); // do not use path.join const safeResult = utils.safeGlobSync(path.join(directory, pattern)); expect(normalResult).toEqual(safeResult); }); }); });