From fd2013a7bf4129b05080f01ef199d69b9ef9ec68 Mon Sep 17 00:00:00 2001 From: Kevin J Hoerr Date: Mon, 27 Apr 2020 18:17:06 -0400 Subject: Refactor startup to passthrough values Even more refactoring - however there were some small troubles using path in the nested scripts/files, so referencing them via the index should be a bit more stable. Plus, the config unit tests won't just exit because of configOrError constants strewn about the file. --- src/index.ts | 11 +++++++--- src/routes.test.ts | 2 +- src/routes.ts | 13 +++++------- src/util/config.test.ts | 38 +++++++++++++++++++++++++++++++---- src/util/config.ts | 53 ++++++++++++++++++------------------------------- 5 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/index.ts b/src/index.ts index e4f8db1..caa4235 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,22 +2,27 @@ import dotenv from "dotenv"; import express from "express"; import winston from "winston"; import expressWinston from "express-winston"; +import path from "path"; dotenv.config(); import routes from "./routes"; import Metadata from "./metadata"; import loggerConfig from "./util/logger"; -import { handleStartup, handleShutdown } from "./util/config"; +import { configOrError, handleStartup, handleShutdown } from "./util/config"; // Start-up configuration const BIND_ADDRESS = process.env.BIND_ADDRESS ?? "localhost"; const MONGO_DB = process.env.MONGO_DB ?? "ao-coverage"; const PORT = Number(process.env.PORT ?? 3000); +const MONGO_URI = configOrError("MONGO_URI"); +const TARGET_URL = process.env.TARGET_URL ?? "http://localhost:3000"; +const PUBLIC_PATH = path.join(__dirname, "..", "public"); +const HOST_DIR = configOrError("HOST_DIR"); const logger = winston.createLogger(loggerConfig("ROOT")); -handleStartup().then(mongo => { +handleStartup(MONGO_URI, HOST_DIR, PUBLIC_PATH, TARGET_URL).then(mongo => { const app: express.Application = express(); const metadata = new Metadata(mongo.db(MONGO_DB)); @@ -32,7 +37,7 @@ handleStartup().then(mongo => { ); // actual app routes - app.use(routes(metadata)); + app.use(routes(metadata, PUBLIC_PATH)); app.use(expressWinston.errorLogger(loggerConfig("_ERR"))); diff --git a/src/routes.test.ts b/src/routes.test.ts index 90084f6..3a6bd26 100644 --- a/src/routes.test.ts +++ b/src/routes.test.ts @@ -38,7 +38,7 @@ const mock = ( const request = (mockMeta: MetadataMockType = mock()): SuperTest => { const app = express(); - app.use(routes(mockMeta as Metadata)); + app.use(routes(mockMeta as Metadata, path.join(__dirname, "..", "public"))); return _request(app); }; diff --git a/src/routes.ts b/src/routes.ts index 7ebb610..98260fa 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -17,7 +17,7 @@ const HOST_DIR = configOrError("HOST_DIR"); const logger = winston.createLogger(loggerConfig("HTTP")); -export default (metadata: Metadata): Router => { +export default (metadata: Metadata, publicPath: string): Router => { const router = Router(); // serve landing page @@ -33,15 +33,12 @@ export default (metadata: Metadata): Router => { }) ); - // serve static files // favicon should be served directly on root router.get("/favicon.ico", (_, res) => { - res.sendFile(path.join(__dirname, "..", "public", "static", "favicon.ico")); + res.sendFile(path.join(publicPath, "static", "favicon.ico")); }); - router.use( - "/static", - express.static(path.join(__dirname, "..", "public", "static")) - ); + // serve static files + router.use("/static", express.static(path.join(publicPath, "static"))); // Upload HTML file router.post("/v1/:org/:repo/:branch/:commit.html", (req, res) => { @@ -219,7 +216,7 @@ export default (metadata: Metadata): Router => { router.use((_, res) => { res.status(404); - res.sendFile(path.join(__dirname, "..", "public", "static", "404.html")); + res.sendFile(path.join(publicPath, "static", "404.html")); }); return router; diff --git a/src/util/config.test.ts b/src/util/config.test.ts index b1e7df3..a065c47 100644 --- a/src/util/config.test.ts +++ b/src/util/config.test.ts @@ -179,6 +179,9 @@ describe("handleStartup", () => { exit.mockClear(); }); + const confStartup = (): Promise => + handleStartup("", "/apple", "/public", "localhost"); + it("should pass back MongoClient", async () => { const superClient = {} as MongoClient; const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue(); @@ -193,7 +196,7 @@ describe("handleStartup", () => { (template: templates.Template) => new Promise(res => res(template)) ); - const result = await handleStartup(); + const result = await confStartup(); expect(fsAccess).toHaveBeenCalledTimes(1); expect(pathAbsolute).toHaveBeenCalledTimes(1); @@ -209,16 +212,29 @@ describe("handleStartup", () => { }); 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(res => res(superClient)) + ); + const processTemplate = jest + .spyOn(templates, "default") + .mockImplementation( + (template: templates.Template) => new Promise(res => res(template)) + ); - const result = await handleStartup(); + 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(); @@ -232,11 +248,21 @@ describe("handleStartup", () => { const mongoClient = jest.spyOn(MongoClient, "connect").mockImplementation( () => new Promise(res => res(superClient)) ); + const processTemplate = jest + .spyOn(templates, "default") + .mockImplementation( + (template: templates.Template) => new Promise(res => res(template)) + ); - await handleStartup(); + 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(); @@ -258,10 +284,14 @@ describe("handleStartup", () => { (template: templates.Template) => new Promise(res => res(template)) ); - await handleStartup(); + 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(); diff --git a/src/util/config.ts b/src/util/config.ts index 195d85b..3eb8d1c 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -45,48 +45,33 @@ export const persistTemplate = async (input: Template): Promise => { } }; -const MONGO_URI = configOrError("MONGO_URI"); -const TARGET_URL = process.env.TARGET_URL ?? "http://localhost:3000"; -const HOST_DIR = configOrError("HOST_DIR"); - -export const handleStartup = async (): Promise => { +export const handleStartup = async ( + mongoUri: string, + hostDir: string, + publicPath: string, + targetUrl: string +): Promise => { try { - await fs.promises.access(HOST_DIR, fs.constants.R_OK | fs.constants.W_OK); - if (!path.isAbsolute(HOST_DIR)) { - logger.error("HOST_DIR must be an absolute path"); - process.exit(1); + 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(MONGO_URI, { + const mongo = await MongoClient.connect(mongoUri, { useUnifiedTopology: true - }).catch((err: MongoError) => { - logger.error(err.message ?? "Unable to connect to database"); - process.exit(1); - }); + }).catch((err: MongoError) => + Promise.reject(err.message ?? "Unable to connect to database") + ); await persistTemplate({ - inputFile: path.join( - __dirname, - "..", - "..", - "public", - "templates", - "bash.template" - ), - outputFile: path.join(HOST_DIR, "bash"), - context: { TARGET_URL } + inputFile: path.join(publicPath, "templates", "bash.template"), + outputFile: path.join(hostDir, "bash"), + context: { targetUrl } } as Template); await persistTemplate({ - inputFile: path.join( - __dirname, - "..", - "..", - "public", - "templates", - "index.html.template" - ), - outputFile: path.join(HOST_DIR, "index.html"), - context: { TARGET_URL } + inputFile: path.join(publicPath, "templates", "index.html.template"), + outputFile: path.join(hostDir, "index.html"), + context: { targetUrl } } as Template); return mongo; -- cgit