aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/config.test.ts472
-rw-r--r--src/util/config.ts122
-rw-r--r--src/util/logger.test.ts6
-rw-r--r--src/util/logger.ts9
4 files changed, 8 insertions, 601 deletions
diff --git a/src/util/config.test.ts b/src/util/config.test.ts
deleted file mode 100644
index 4343392..0000000
--- a/src/util/config.test.ts
+++ /dev/null
@@ -1,472 +0,0 @@
-const exit = jest
- .spyOn(process, "exit")
- .mockImplementation(() => undefined as never);
-
-import { Writable } from "stream";
-import winston from "winston";
-
-let output = "";
-
-jest.mock("./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();
-
- // 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");
- } 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");
-
- // 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);
-
- 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);
-
- 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);
-
- 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",
- } as EnvConfig;
- const confStartup = (): Promise<MongoClient> =>
- handleStartup("", config, "localhost");
-
- 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)("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)("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)("SIGINT");
- } catch (err) {
- //
- }
-
- // Assert
- expect(exit).toHaveBeenCalledWith(1);
- });
-});
diff --git a/src/util/config.ts b/src/util/config.ts
deleted file mode 100644
index c8639fb..0000000
--- a/src/util/config.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import winston from "winston";
-import { MongoClient, MongoError } from "mongodb";
-import { Server } from "http";
-import path from "path";
-import fs from "fs";
-import { v4 as uuid } from "uuid";
-
-import loggerConfig from "./logger";
-import processTemplate, { Template } from "../templates";
-import { EnvConfig } from "../metadata";
-
-const logger = winston.createLogger(loggerConfig("ROOT"));
-
-export const initializeToken = (): string => {
- //TODO check for token in hostDir/persist created token in hostDir so it's not regenerated on startup
- const newToken = uuid();
-
- logger.warn(
- "TOKEN variable not provided, using this value instead: %s",
- newToken
- );
- logger.warn(
- "Use this provided token to push your coverage reports to the server."
- );
-
- return newToken;
-};
-
-export const configOrError = (varName: string): string => {
- const value = process.env[varName];
- if (value !== undefined) {
- return value;
- } else {
- logger.error("%s must be defined", varName);
- process.exit(1);
- }
-};
-
-export const persistTemplate = async (input: Template): Promise<void> => {
- try {
- const template = await processTemplate(input);
- logger.debug("Generated '%s' from template file", template.outputFile);
- } catch (err1) {
- try {
- await fs.promises.access(input.outputFile, fs.constants.R_OK);
- } catch (err2) {
- logger.error(
- "Error while generating '%s' from template file: %s",
- input.outputFile,
- err1
- );
- logger.error("Cannot proceed due to error: %s", err2);
-
- process.exit(1);
- }
- // if the output file exists, then we are fine with continuing without
- logger.warn(
- "Could not generate '%s' from template file, but file already exists: %s",
- input.outputFile,
- err1
- );
- }
-};
-
-export const handleStartup = async (
- mongoUri: string,
- config: EnvConfig,
- targetUrl: string
-): Promise<MongoClient> => {
- try {
- const { hostDir, publicDir } = config;
- await fs.promises.access(hostDir, fs.constants.R_OK | fs.constants.W_OK);
- if (!path.isAbsolute(hostDir)) {
- await Promise.reject("hostDir must be an absolute path");
- }
-
- const mongo = await MongoClient.connect(mongoUri).catch((err: MongoError) =>
- Promise.reject(err.message ?? "Unable to connect to database")
- );
-
- await persistTemplate({
- inputFile: path.join(publicDir, "templates", "sh.tmpl"),
- outputFile: path.join(hostDir, "sh"),
- context: { TARGET_URL: targetUrl },
- } as Template);
- await persistTemplate({
- inputFile: path.join(publicDir, "templates", "index.html.tmpl"),
- outputFile: path.join(hostDir, "index.html"),
- context: {
- TARGET_URL: targetUrl,
- CURL_HTTPS: targetUrl.includes("https")
- ? "--proto '=https' --tlsv1.2 "
- : "",
- },
- } as Template);
-
- return mongo;
- } catch (err) {
- logger.error("Error occurred during startup: %s", err);
- process.exit(1);
- }
-};
-
-export const handleShutdown =
- (mongo: MongoClient, server: Server) =>
- (signal: NodeJS.Signals): Promise<void> => {
- logger.warn("%s signal received. Closing shop.", signal);
-
- return mongo
- .close()
- .then(() => {
- logger.info("MongoDB client connection closed.");
- return new Promise((res, rej) =>
- server.close((err) => {
- logger.info("Express down.");
- (err ? rej : res)(err);
- })
- );
- })
- .then(() => process.exit(0))
- .catch(() => process.exit(1));
- };
diff --git a/src/util/logger.test.ts b/src/util/logger.test.ts
index a673bad..a7e81c1 100644
--- a/src/util/logger.test.ts
+++ b/src/util/logger.test.ts
@@ -12,7 +12,7 @@ describe("Logger configurer", () => {
};
// Act
- const result = configureLogger(clazz);
+ const result = configureLogger(clazz, adapter.level);
const actual = result.format.transform(Object.assign({}, adapter));
// Assert
@@ -34,7 +34,7 @@ describe("Logger configurer", () => {
};
// Act
- const result = configureLogger(label);
+ const result = configureLogger(label, adapter.level);
const actual = result.format.transform(Object.assign({}, adapter));
// Assert
@@ -51,7 +51,7 @@ describe("Logger configurer", () => {
const label = "aaa";
// Act
- const result = configureLogger(label);
+ const result = configureLogger(label, "info");
// Assert
expect(result.transports).toBeInstanceOf(Array);
diff --git a/src/util/logger.ts b/src/util/logger.ts
index d779718..9b0957d 100644
--- a/src/util/logger.ts
+++ b/src/util/logger.ts
@@ -4,8 +4,7 @@ import * as Transport from "winston-transport";
const { combine, splat, timestamp, label, colorize, printf } = winston.format;
const { Console } = winston.transports;
-const LOG_LEVEL = process.env.LOG_LEVEL ?? "info";
-
+// Standard console message formatting
const consoleFormat = combine(
colorize(),
printf(({ level, message, label, timestamp }) => {
@@ -16,9 +15,9 @@ const consoleFormat = combine(
/**
* Provides standard logging format and output for the server.
*/
-export default (
+const loggerConfig = (
clazz: string,
- level: string = LOG_LEVEL
+ level: string
): {
format: Format;
transports: Transport[];
@@ -26,3 +25,5 @@ export default (
format: combine(splat(), timestamp(), label({ label: clazz })),
transports: [new Console({ level: level, format: consoleFormat })],
});
+
+export default loggerConfig;