aboutsummaryrefslogtreecommitdiff
path: root/src/config.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/config.test.ts')
-rw-r--r--src/config.test.ts475
1 files changed, 475 insertions, 0 deletions
diff --git a/src/config.test.ts b/src/config.test.ts
new file mode 100644
index 0000000..c7a8be4
--- /dev/null
+++ b/src/config.test.ts
@@ -0,0 +1,475 @@
+const exit = jest
+ .spyOn(process, "exit")
+ .mockImplementation(() => undefined as never);
+
+import { Writable } from "stream";
+import winston from "winston";
+import loggerConfig from "./util/logger";
+
+let output = "";
+const logger = winston.createLogger(loggerConfig("TEST", "debug"));
+
+jest.mock("./util/logger", () => {
+ const stream = new Writable();
+ stream._write = (chunk, _encoding, next) => {
+ output = output += chunk.toString();
+ next();
+ };
+ const streamTransport = new winston.transports.Stream({ stream });
+
+ return {
+ __esModule: true,
+ default: () => ({
+ format: winston.format.combine(
+ winston.format.splat(),
+ winston.format.simple()
+ ),
+ transports: [streamTransport],
+ }),
+ };
+});
+
+import {
+ configOrError,
+ persistTemplate,
+ handleStartup,
+ handleShutdown,
+ initializeToken,
+} from "./config";
+import {
+ Logger,
+ MongoClient,
+ MongoError,
+ ReadConcern,
+ ReadPreference,
+ WriteConcern,
+} from "mongodb";
+import { Server } from "http";
+import path from "path";
+import fs from "fs";
+import * as templates from "./templates";
+import { EnvConfig } from "./metadata";
+
+const CommonMocks = {
+ connect: jest.fn(),
+ isConnected: jest.fn(),
+ logout: jest.fn(),
+ watch: jest.fn(),
+ startSession: jest.fn(),
+ withSession: jest.fn(),
+ addListener: jest.fn(),
+ on: jest.fn(),
+ once: jest.fn(),
+ prependListener: jest.fn(),
+ prependOnceListener: jest.fn(),
+ removeAllListeners: jest.fn(),
+ removeListener: jest.fn(),
+ off: jest.fn(),
+ setMaxListeners: jest.fn(),
+ getMaxListeners: jest.fn(),
+ listeners: jest.fn(),
+ listenerCount: jest.fn(),
+ rawListeners: jest.fn(),
+ emit: jest.fn(),
+ eventNames: jest.fn(),
+};
+
+const MongoMock = (p: Promise<void>): jest.Mock<MongoClient, void[]> =>
+ jest.fn<MongoClient, void[]>(() => ({
+ ...CommonMocks,
+ close: jest.fn(() => p),
+ readPreference: ReadPreference.nearest,
+ bsonOptions: {},
+ logger: new Logger("a"),
+ getLogger: jest.fn(),
+ options: {
+ hosts: [],
+ readPreference: ReadPreference.nearest,
+ readConcern: new ReadConcern("local"),
+ loadBalanced: true,
+ serverApi: { version: "1" },
+ compressors: [],
+ writeConcern: new WriteConcern(),
+ dbName: "",
+ metadata: {
+ driver: { name: "", version: "" },
+ os: { type: "", name: "linux", architecture: "", version: "" },
+ platform: "linx",
+ },
+ tls: true,
+ toURI: jest.fn(),
+ autoEncryption: {},
+ connectTimeoutMS: 0,
+ directConnection: true,
+ driverInfo: {},
+ forceServerObjectId: true,
+ minHeartbeatFrequencyMS: 0,
+ heartbeatFrequencyMS: 0,
+ keepAlive: false,
+ keepAliveInitialDelay: 0,
+ localThresholdMS: 0,
+ logger: new Logger("a"),
+ maxIdleTimeMS: 0,
+ maxPoolSize: 0,
+ minPoolSize: 0,
+ monitorCommands: true,
+ noDelay: true,
+ pkFactory: { createPk: jest.fn() },
+ promiseLibrary: {},
+ raw: true,
+ replicaSet: "",
+ retryReads: true,
+ retryWrites: true,
+ serverSelectionTimeoutMS: 0,
+ socketTimeoutMS: 0,
+ tlsAllowInvalidCertificates: true,
+ tlsAllowInvalidHostnames: true,
+ tlsInsecure: false,
+ waitQueueTimeoutMS: 0,
+ zlibCompressionLevel: 0,
+ srvMaxHosts: 1,
+ srvServiceName: "",
+ },
+ serverApi: { version: "1" },
+ autoEncrypter: undefined,
+ readConcern: new ReadConcern("local"),
+ writeConcern: new WriteConcern(),
+ db: jest.fn(),
+ }));
+const ServerMock = (mockErr: Error | undefined): jest.Mock<Server, void[]> =>
+ jest.fn<Server, void[]>(() => ({
+ ...CommonMocks,
+ connections: 0,
+ setTimeout: jest.fn(),
+ timeout: 0,
+ headersTimeout: 0,
+ keepAliveTimeout: 0,
+ close: function (c: (err?: Error | undefined) => void): Server {
+ c(mockErr);
+ return this;
+ },
+ maxHeadersCount: 0,
+ maxConnections: 0,
+ maxRequestsPerSocket: 0,
+ listen: jest.fn(),
+ listening: true,
+ address: jest.fn(),
+ getConnections: jest.fn(),
+ ref: jest.fn(),
+ requestTimeout: 3600,
+ unref: jest.fn(),
+ }));
+
+describe("initializeToken", () => {
+ it("Should generate a UUID", () => {
+ // Arrange
+ output = "";
+
+ // Act
+ const result = initializeToken(logger);
+
+ // Assert
+ expect(result).toMatch(/([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})/);
+ expect(output).toContain(result);
+ });
+});
+
+describe("configOrError", () => {
+ beforeEach(() => {
+ exit.mockClear();
+ });
+
+ it("should exit when a env var does not exist", () => {
+ // Arrange
+
+ // Act
+ let result;
+ try {
+ result = configOrError("APPLESAUCE", logger);
+ } catch (err) {
+ //
+ }
+
+ // Assert
+ expect(result).toBeUndefined();
+ expect(exit).toHaveBeenCalledWith(1);
+ });
+
+ it("should return the expected env var", () => {
+ // Arrange
+ process.env.CHRYSANTHEMUM = "hello";
+
+ // Act
+ const result = configOrError("CHRYSANTHEMUM", logger);
+
+ // Assert
+ expect(result).toEqual(process.env.CHRYSANTHEMUM);
+ expect(exit).toHaveBeenCalledTimes(0);
+ });
+});
+
+describe("persistTemplate", () => {
+ beforeEach(() => {
+ exit.mockClear();
+ });
+
+ it("should generate a template without error", async () => {
+ const template = {
+ inputFile: "/mnt/c/Windows/System32",
+ outputFile: "./helloworld.txt",
+ context: { EXAMPLE: "this" },
+ } as templates.Template;
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockImplementation(
+ (template: templates.Template) => new Promise((res) => res(template))
+ );
+ const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
+
+ await persistTemplate(template, logger);
+
+ expect(processTemplate).toHaveBeenCalledWith(template);
+ expect(fsAccess).not.toHaveBeenCalled();
+ expect(exit).not.toHaveBeenCalled();
+ processTemplate.mockRestore();
+ fsAccess.mockRestore();
+ });
+
+ it("should exit without error if template does not generate but file already exists", async () => {
+ const template = {
+ inputFile: "/mnt/c/Windows/System32",
+ outputFile: "./helloworld.txt",
+ context: { EXAMPLE: "this" },
+ } as templates.Template;
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockRejectedValue("baa");
+ const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
+
+ await persistTemplate(template, logger);
+
+ expect(processTemplate).toHaveBeenCalledWith(template);
+ expect(fsAccess).toHaveBeenCalledWith(
+ template.outputFile,
+ fs.constants.R_OK
+ );
+ expect(exit).not.toHaveBeenCalled();
+ processTemplate.mockRestore();
+ fsAccess.mockRestore();
+ });
+
+ it("should exit with error if template does not generate and file does not exist", async () => {
+ const template = {
+ inputFile: "/mnt/c/Windows/System32",
+ outputFile: "./helloworld.txt",
+ context: { EXAMPLE: "this" },
+ } as templates.Template;
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockRejectedValue("baz");
+ const fsAccess = jest.spyOn(fs.promises, "access").mockRejectedValue("bar");
+
+ await persistTemplate(template, logger);
+
+ expect(processTemplate).toHaveBeenCalledWith(template);
+ expect(fsAccess).toHaveBeenCalledWith(
+ template.outputFile,
+ fs.constants.R_OK
+ );
+ expect(exit).toHaveBeenCalledWith(1);
+ processTemplate.mockRestore();
+ fsAccess.mockRestore();
+ });
+});
+
+describe("handleStartup", () => {
+ beforeEach(() => {
+ exit.mockClear();
+ });
+
+ const config = {
+ hostDir: "/apple",
+ publicDir: "/public",
+ targetUrl: "localhost"
+ } as EnvConfig;
+ const confStartup = (): Promise<MongoClient> =>
+ handleStartup(config, logger);
+
+ it("should pass back MongoClient", async () => {
+ const superClient = {} as MongoClient;
+ const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
+ const pathAbsolute = jest.spyOn(path, "isAbsolute").mockReturnValue(true);
+ const pathJoin = jest.spyOn(path, "join").mockReturnValue("path");
+ const mongoClient = jest
+ .spyOn(MongoClient, "connect")
+ .mockImplementation(
+ () => new Promise<MongoClient>((res) => res(superClient))
+ );
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockImplementation(
+ (template: templates.Template) => new Promise((res) => res(template))
+ );
+
+ const result = await confStartup();
+
+ expect(fsAccess).toHaveBeenCalledTimes(1);
+ expect(pathAbsolute).toHaveBeenCalledTimes(1);
+ expect(mongoClient).toHaveBeenCalledTimes(1);
+ expect(processTemplate).toHaveBeenCalledTimes(2);
+ expect(exit).not.toHaveBeenCalled();
+ expect(result).toEqual(superClient);
+ processTemplate.mockRestore();
+ mongoClient.mockRestore();
+ pathAbsolute.mockRestore();
+ pathJoin.mockRestore();
+ fsAccess.mockRestore();
+ });
+
+ it("should exit if HOST_DIR is not read/write accessible", async () => {
+ const superClient = {} as MongoClient;
+ const fsAccess = jest.spyOn(fs.promises, "access").mockRejectedValue("boo");
+ const pathAbsolute = jest.spyOn(path, "isAbsolute").mockReturnValue(true);
+ const pathJoin = jest.spyOn(path, "join").mockReturnValue("path");
+ const mongoClient = jest
+ .spyOn(MongoClient, "connect")
+ .mockImplementation(
+ () => new Promise<MongoClient>((res) => res(superClient))
+ );
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockImplementation(
+ (template: templates.Template) => new Promise((res) => res(template))
+ );
+
+ const result = await confStartup();
+
+ expect(fsAccess).toHaveBeenCalledTimes(1);
+ expect(exit).toHaveBeenCalledWith(1);
+ expect(pathAbsolute).not.toHaveBeenCalled();
+ expect(mongoClient).not.toHaveBeenCalled();
+ expect(processTemplate).not.toHaveBeenCalled();
+ expect(result).toBeUndefined();
+ processTemplate.mockRestore();
+ mongoClient.mockRestore();
+ pathAbsolute.mockRestore();
+ pathJoin.mockRestore();
+ fsAccess.mockRestore();
+ });
+
+ it("should exit if HOST_DIR is not absolute path", async () => {
+ const superClient = {} as MongoClient;
+ const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
+ const pathAbsolute = jest.spyOn(path, "isAbsolute").mockReturnValue(false);
+ const pathJoin = jest.spyOn(path, "join").mockReturnValue("path");
+ const mongoClient = jest
+ .spyOn(MongoClient, "connect")
+ .mockImplementation(
+ () => new Promise<MongoClient>((res) => res(superClient))
+ );
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockImplementation(
+ (template: templates.Template) => new Promise((res) => res(template))
+ );
+
+ const result = await confStartup();
+
+ expect(fsAccess).toHaveBeenCalledTimes(1);
+ expect(pathAbsolute).toHaveBeenCalledTimes(1);
+ expect(exit).toHaveBeenCalledWith(1);
+ expect(mongoClient).not.toHaveBeenCalled();
+ expect(processTemplate).not.toHaveBeenCalled();
+ expect(result).toBeUndefined();
+ processTemplate.mockRestore();
+ mongoClient.mockRestore();
+ pathAbsolute.mockRestore();
+ pathJoin.mockRestore();
+ fsAccess.mockRestore();
+ });
+
+ it("should exit if MongoClient has error", async () => {
+ const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
+ const pathAbsolute = jest.spyOn(path, "isAbsolute").mockReturnValue(true);
+ const pathJoin = jest.spyOn(path, "join").mockReturnValue("path");
+ const mongoClient = jest
+ .spyOn(MongoClient, "connect")
+ .mockImplementation(
+ () => new Promise((_, rej) => rej({ message: "aaahhh" } as MongoError))
+ );
+ const processTemplate = jest
+ .spyOn(templates, "default")
+ .mockImplementation(
+ (template: templates.Template) => new Promise((res) => res(template))
+ );
+
+ const result = await confStartup();
+
+ expect(fsAccess).toHaveBeenCalledTimes(1);
+ expect(pathAbsolute).toHaveBeenCalledTimes(1);
+ expect(mongoClient).toHaveBeenCalledTimes(1);
+ expect(exit).toHaveBeenCalledWith(1);
+ expect(processTemplate).not.toHaveBeenCalled();
+ expect(result).toBeUndefined();
+ processTemplate.mockRestore();
+ mongoClient.mockRestore();
+ pathAbsolute.mockRestore();
+ pathJoin.mockRestore();
+ fsAccess.mockRestore();
+ });
+});
+
+describe("handleShutdown", () => {
+ beforeEach(() => {
+ exit.mockClear();
+ // we don't use the MongoMock or ServerMock to directly test, so no mockClear needed
+ });
+
+ it("should exit gracefully without error", async () => {
+ // Arrange
+ const mongo = MongoMock(new Promise((r) => r()))();
+ const server = ServerMock(undefined)();
+
+ // Act
+ try {
+ await handleShutdown(mongo, server, logger)("SIGINT");
+ } catch (err) {
+ //
+ }
+
+ // Assert
+ expect(exit).toHaveBeenCalledWith(0);
+ });
+
+ it("should exit with error with Mongo error", async () => {
+ // Arrange
+ const mongo = MongoMock(new Promise((_, r) => r()))();
+ const server = ServerMock(undefined)();
+
+ // Act
+ try {
+ await handleShutdown(mongo, server, logger)("SIGINT");
+ } catch (err) {
+ //
+ }
+
+ // Assert
+ expect(exit).toHaveBeenCalledWith(1);
+ });
+
+ it("should exit with error with Server error", async () => {
+ // Arrange
+ const mongo = MongoMock(new Promise((r) => r()))();
+ const server = ServerMock(Error("oh noooo"))();
+
+ // Act
+ try {
+ await handleShutdown(mongo, server, logger)("SIGINT");
+ } catch (err) {
+ //
+ }
+
+ // Assert
+ expect(exit).toHaveBeenCalledWith(1);
+ });
+});