aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin J Hoerr <kjhoerr@protonmail.com>2022-04-07 06:13:09 +0000
committerKevin J Hoerr <kjhoerr@protonmail.com>2022-04-07 06:13:09 +0000
commit4af6310a9c42fbc81ab82d6253becf1f3fdeebac (patch)
tree26cb1d976912a32879121746500bb9a69417a885
parent4ba501e2caea4d6dc483ae7f8779031810700228 (diff)
downloadao-coverage-4af6310a9c42fbc81ab82d6253becf1f3fdeebac.tar.gz
ao-coverage-4af6310a9c42fbc81ab82d6253becf1f3fdeebac.tar.bz2
ao-coverage-4af6310a9c42fbc81ab82d6253becf1f3fdeebac.zip
#17 Move TOKEN from EnvConfig to database
-rw-r--r--.vscode/launch.json15
-rw-r--r--.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zipbin0 -> 53912 bytes
-rw-r--r--.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zipbin0 -> 3751 bytes
-rw-r--r--.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zipbin0 -> 4560 bytes
-rw-r--r--.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zipbin0 -> 6878 bytes
-rw-r--r--.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zipbin0 -> 41301 bytes
-rw-r--r--.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zipbin0 -> 4890 bytes
-rw-r--r--.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zipbin0 -> 8117 bytes
-rw-r--r--.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zipbin0 -> 20114 bytes
-rw-r--r--.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zipbin0 -> 71562 bytes
-rw-r--r--.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zipbin0 -> 44979 bytes
-rw-r--r--.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zipbin0 -> 6690 bytes
-rw-r--r--.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zipbin0 -> 3604 bytes
-rw-r--r--.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zipbin0 -> 64886 bytes
-rw-r--r--.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zipbin0 -> 5694 bytes
-rw-r--r--.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zipbin0 -> 12839 bytes
-rw-r--r--.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zipbin0 -> 2750 bytes
-rw-r--r--package.json2
-rw-r--r--src/config.test.ts258
-rw-r--r--src/config.ts35
-rw-r--r--src/index.ts15
-rw-r--r--src/metadata.test.ts60
-rw-r--r--src/metadata.ts81
-rw-r--r--src/routes.test.ts11
-rw-r--r--src/routes.ts99
-rw-r--r--yarn.lock181
26 files changed, 440 insertions, 317 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1ab3567..957ff80 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -15,6 +15,21 @@
"outFiles": [
"${workspaceFolder}/**/*.js"
]
+ },
+ {
+ "type": "node",
+ "name": "vscode-jest-tests",
+ "request": "launch",
+ "console": "integratedTerminal",
+ "internalConsoleOptions": "neverOpen",
+ "disableOptimisticBPs": true,
+ "cwd": "/workspace/src",
+ "runtimeExecutable": "yarn",
+ "args": [
+ "jest",
+ "--runInBand",
+ "--watchAll=false"
+ ]
}
]
} \ No newline at end of file
diff --git a/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip b/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip
new file mode 100644
index 0000000..0af20fc
--- /dev/null
+++ b/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip
Binary files differ
diff --git a/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip b/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip
new file mode 100644
index 0000000..24c07c9
--- /dev/null
+++ b/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip
Binary files differ
diff --git a/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip b/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip
new file mode 100644
index 0000000..6b14888
--- /dev/null
+++ b/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip
Binary files differ
diff --git a/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip b/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip
new file mode 100644
index 0000000..41d8c66
--- /dev/null
+++ b/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip
Binary files differ
diff --git a/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip b/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip
new file mode 100644
index 0000000..7f16cd9
--- /dev/null
+++ b/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip
Binary files differ
diff --git a/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip b/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip
new file mode 100644
index 0000000..625a79f
--- /dev/null
+++ b/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip
Binary files differ
diff --git a/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip b/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip
new file mode 100644
index 0000000..f4bfff0
--- /dev/null
+++ b/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip
Binary files differ
diff --git a/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip b/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip
new file mode 100644
index 0000000..92db251
--- /dev/null
+++ b/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip
Binary files differ
diff --git a/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip b/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip
new file mode 100644
index 0000000..038beb4
--- /dev/null
+++ b/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip
Binary files differ
diff --git a/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip b/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip
new file mode 100644
index 0000000..db222e2
--- /dev/null
+++ b/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip
Binary files differ
diff --git a/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip b/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip
new file mode 100644
index 0000000..d2eec07
--- /dev/null
+++ b/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip
Binary files differ
diff --git a/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip b/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip
new file mode 100644
index 0000000..9b4c088
--- /dev/null
+++ b/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip
Binary files differ
diff --git a/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip b/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip
new file mode 100644
index 0000000..2e6949b
--- /dev/null
+++ b/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip
Binary files differ
diff --git a/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip b/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip
new file mode 100644
index 0000000..96867a6
--- /dev/null
+++ b/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip
Binary files differ
diff --git a/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip b/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip
new file mode 100644
index 0000000..5deef33
--- /dev/null
+++ b/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip
Binary files differ
diff --git a/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip b/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip
new file mode 100644
index 0000000..4dc7fcc
--- /dev/null
+++ b/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip
Binary files differ
diff --git a/package.json b/package.json
index 2c1d74e..01d60c5 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"license": "Parity-7.0.0",
"dependencies": {
"badgen": "^3.2.2",
+ "bcrypt": "^5.0.1",
"bson": "^4.6.2",
"dotenv": "^16.0.0",
"express": "^4.17.3",
@@ -39,6 +40,7 @@
},
"devDependencies": {
"@microsoft/tsdoc": "^0.13.2",
+ "@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/jsdom": "^16.2.14",
diff --git a/src/config.test.ts b/src/config.test.ts
index b7c869f..456a6e3 100644
--- a/src/config.test.ts
+++ b/src/config.test.ts
@@ -2,177 +2,34 @@ 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 { MongoClient, MongoError } from "mongodb";
import { Server } from "http";
import path from "path";
import fs from "fs";
+
+import loggerConfig from "./util/logger";
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 = "";
+import Metadata, { EnvConfig } from "./metadata";
- // Act
- const result = initializeToken(logger);
+jest.mock("./util/logger", () => ({
+ __esModule: true,
+ default: () => ({
+ format: winston.format.combine(
+ winston.format.splat(),
+ winston.format.simple()
+ ),
+ transports: [new winston.transports.Console({ silent: true })],
+ }),
+}));
- // Assert
- expect(result).toMatch(/([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})/);
- expect(output).toContain(result);
- });
-});
+const LOGGER = winston.createLogger(loggerConfig("TEST", "debug"));
describe("configOrError", () => {
beforeEach(() => {
@@ -185,7 +42,7 @@ describe("configOrError", () => {
// Act
let result;
try {
- result = configOrError("APPLESAUCE", logger);
+ result = configOrError("APPLESAUCE", LOGGER);
} catch (err) {
//
}
@@ -200,7 +57,7 @@ describe("configOrError", () => {
process.env.CHRYSANTHEMUM = "hello";
// Act
- const result = configOrError("CHRYSANTHEMUM", logger);
+ const result = configOrError("CHRYSANTHEMUM", LOGGER);
// Assert
expect(result).toEqual(process.env.CHRYSANTHEMUM);
@@ -226,7 +83,7 @@ describe("persistTemplate", () => {
);
const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
- await persistTemplate(template, logger);
+ await persistTemplate(template, LOGGER);
expect(processTemplate).toHaveBeenCalledWith(template);
expect(fsAccess).not.toHaveBeenCalled();
@@ -246,7 +103,7 @@ describe("persistTemplate", () => {
.mockRejectedValue("baa");
const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue();
- await persistTemplate(template, logger);
+ await persistTemplate(template, LOGGER);
expect(processTemplate).toHaveBeenCalledWith(template);
expect(fsAccess).toHaveBeenCalledWith(
@@ -269,7 +126,7 @@ describe("persistTemplate", () => {
.mockRejectedValue("baz");
const fsAccess = jest.spyOn(fs.promises, "access").mockRejectedValue("bar");
- await persistTemplate(template, logger);
+ await persistTemplate(template, LOGGER);
expect(processTemplate).toHaveBeenCalledWith(template);
expect(fsAccess).toHaveBeenCalledWith(
@@ -290,40 +147,13 @@ describe("handleStartup", () => {
const config = {
hostDir: "/apple",
publicDir: "/public",
+ dbUri: "localhost:27017",
+ dbName: "bongo",
targetUrl: "localhost",
+ logLevel: "trace",
} 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();
- });
+ const confStartup = (): Promise<Metadata> =>
+ handleStartup(config, undefined, LOGGER);
it("should exit if HOST_DIR is not read/write accessible", async () => {
const superClient = {} as MongoClient;
@@ -426,12 +256,18 @@ describe("handleShutdown", () => {
it("should exit gracefully without error", async () => {
// Arrange
- const mongo = MongoMock(new Promise((r) => r()))();
- const server = ServerMock(undefined)();
+ const metadata = {
+ close: () => Promise.resolve(),
+ } as Metadata;
+ const server = {
+ close: (callback?: ((err?: Error | undefined) => void) | undefined) => {
+ callback !== undefined && callback();
+ },
+ } as Server;
// Act
try {
- await handleShutdown(mongo, server, logger)("SIGINT");
+ await handleShutdown(metadata, server, LOGGER)("SIGINT");
} catch (err) {
//
}
@@ -440,14 +276,20 @@ describe("handleShutdown", () => {
expect(exit).toHaveBeenCalledWith(0);
});
- it("should exit with error with Mongo error", async () => {
+ it("should exit with error with Metadata error", async () => {
// Arrange
- const mongo = MongoMock(new Promise((_, r) => r()))();
- const server = ServerMock(undefined)();
+ const metadata = {
+ close: () => Promise.reject(),
+ } as Metadata;
+ const server = {
+ close: (callback?: ((err?: Error | undefined) => void) | undefined) => {
+ callback !== undefined && callback();
+ },
+ } as Server;
// Act
try {
- await handleShutdown(mongo, server, logger)("SIGINT");
+ await handleShutdown(metadata, server, LOGGER)("SIGINT");
} catch (err) {
//
}
@@ -458,12 +300,18 @@ describe("handleShutdown", () => {
it("should exit with error with Server error", async () => {
// Arrange
- const mongo = MongoMock(new Promise((r) => r()))();
- const server = ServerMock(Error("oh noooo"))();
+ const metadata = {
+ close: () => Promise.resolve(),
+ } as Metadata;
+ const server = {
+ close: (callback?: ((err?: Error | undefined) => void) | undefined) => {
+ callback !== undefined && callback(Error("NOOO"));
+ },
+ } as Server;
// Act
try {
- await handleShutdown(mongo, server, logger)("SIGINT");
+ await handleShutdown(metadata, server, LOGGER)("SIGINT");
} catch (err) {
//
}
diff --git a/src/config.ts b/src/config.ts
index 1908c10..4f4a970 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -3,28 +3,9 @@ import { MongoClient, MongoError } from "mongodb";
import { Server } from "http";
import path from "path";
import fs from "fs";
-import { v4 as uuid } from "uuid";
import processTemplate, { Template } from "./templates";
-import { EnvConfig } from "./metadata";
-
-/**
- * Generate a token for use as the user self-identifier
- */
-export const initializeToken = (logger: winston.Logger): 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;
-};
+import Metadata, { EnvConfig } from "./metadata";
/**
* Get environment variable or exit application if it doesn't exist
@@ -84,8 +65,9 @@ export const persistTemplate = async (
*/
export const handleStartup = async (
config: EnvConfig,
+ token: string | undefined,
logger: winston.Logger
-): Promise<MongoClient> => {
+): Promise<Metadata> => {
try {
const { hostDir, publicDir, dbUri, targetUrl } = config;
await fs.promises.access(hostDir, fs.constants.R_OK | fs.constants.W_OK);
@@ -119,7 +101,11 @@ export const handleStartup = async (
logger
);
- return mongo;
+ const metadata = new Metadata(mongo, config);
+
+ await metadata.initializeToken(token);
+
+ return metadata;
} catch (err) {
logger.error("Error occurred during startup: %s", err);
process.exit(1);
@@ -131,13 +117,12 @@ export const handleStartup = async (
* and close open connections.
*/
export const handleShutdown =
- (mongo: MongoClient, server: Server, logger: winston.Logger) =>
+ (metadata: Metadata, server: Server, logger: winston.Logger) =>
async (signal: NodeJS.Signals): Promise<void> => {
logger.warn("%s signal received. Closing shop.", signal);
try {
- await mongo.close();
- logger.info("MongoDB client connection closed.");
+ await metadata.close();
// must await for callback - wrapped in Promise
await new Promise((res, rej) =>
diff --git a/src/index.ts b/src/index.ts
index ae15509..dcfc011 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,14 +5,9 @@ import expressWinston from "express-winston";
import path from "path";
import routes from "./routes";
-import Metadata, { EnvConfig } from "./metadata";
+import { EnvConfig } from "./metadata";
import loggerConfig from "./util/logger";
-import {
- configOrError,
- handleStartup,
- handleShutdown,
- initializeToken,
-} from "./config";
+import { configOrError, handleStartup, handleShutdown } from "./config";
dotenv.config();
@@ -38,15 +33,13 @@ const ENV_CONFIG: EnvConfig = {
hostDir: configOrError("HOST_DIR", logger),
// Application configuration
- token: process.env.TOKEN ?? initializeToken(logger),
stage1: Number(process.env.STAGE_1 ?? 95),
stage2: Number(process.env.STAGE_2 ?? 80),
logLevel: LOG_LEVEL,
};
-handleStartup(ENV_CONFIG, logger).then((mongo) => {
+handleStartup(ENV_CONFIG, process.env.TOKEN, logger).then((metadata) => {
const app: express.Application = express();
- const metadata = new Metadata(mongo.db(ENV_CONFIG.dbName), ENV_CONFIG);
app.use(
expressWinston.logger({
@@ -75,6 +68,6 @@ handleStartup(ENV_CONFIG, logger).then((mongo) => {
// application exit handling
const signalCodes: NodeJS.Signals[] = ["SIGTERM", "SIGHUP", "SIGINT"];
signalCodes.map((code: NodeJS.Signals) => {
- process.on(code, handleShutdown(mongo, server, logger));
+ process.on(code, handleShutdown(metadata, server, logger));
});
});
diff --git a/src/metadata.test.ts b/src/metadata.test.ts
index 439bf7e..62a235c 100644
--- a/src/metadata.test.ts
+++ b/src/metadata.test.ts
@@ -1,18 +1,44 @@
-import Metadata, { isError, EnvConfig, HeadIdentity } from "./metadata";
import { Db, MongoClient, Collection } from "mongodb";
+import { Writable } from "stream";
+import winston from "winston";
import { BranchNotFoundError } from "./errors";
+import Metadata, { isError, EnvConfig, HeadIdentity } from "./metadata";
jest.mock("mongodb");
+let output = "";
+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],
+ }),
+ };
+});
+
const defaultEnvConfig = {
- token: "llama",
uploadLimit: 12,
hostDir: "pineapple",
publicDir: ".dir",
stage1: 132,
stage2: 1.0,
};
-const defaultMetadata = () =>
- new Metadata(new Db({} as MongoClient, ""), defaultEnvConfig as EnvConfig);
+const defaultMetadata = () => {
+ jest
+ .spyOn(MongoClient.prototype, "db")
+ .mockImplementation(() => new Db({} as MongoClient, ""));
+ return new Metadata(new MongoClient(""), defaultEnvConfig as EnvConfig);
+};
describe("isError", () => {
it("should return false when object is a HeadContext", () => {
@@ -372,16 +398,34 @@ describe("createRepository", () => {
});
});
-describe("getToken", () => {
- it("should return the token from EnvConfig", () => {
+describe("initializeToken", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("Should generate a UUID", async () => {
// Arrange
const metadata = defaultMetadata();
+ const collectionMethod = jest
+ .spyOn(Db.prototype, "collection")
+ .mockImplementation(() => new Collection());
+ const countMethod = jest
+ .spyOn(Collection.prototype, "countDocuments")
+ .mockImplementation(() => Promise.resolve(0));
+ const replaceMethod = jest
+ .spyOn(Collection.prototype, "findOneAndReplace")
+ .mockImplementation(() => Promise.resolve({ ok: 1 }));
+ output = "";
// Act
- const result = metadata.getToken();
+ const result = await metadata.initializeToken();
// Assert
- expect(result).toEqual(defaultEnvConfig.token);
+ expect(result).toEqual(true);
+ expect(output).toMatch(/([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})/);
+ expect(collectionMethod).toHaveBeenCalledTimes(2);
+ expect(countMethod).toHaveBeenCalledTimes(1);
+ expect(replaceMethod).toHaveBeenCalledTimes(1);
});
});
diff --git a/src/metadata.ts b/src/metadata.ts
index 184580f..8899e37 100644
--- a/src/metadata.ts
+++ b/src/metadata.ts
@@ -1,5 +1,7 @@
-import { Db } from "mongodb";
+import { Db, MongoClient } from "mongodb";
import winston from "winston";
+import bcrypt from "bcrypt";
+import { v4 as uuid } from "uuid";
import loggerConfig from "./util/logger";
import { BranchNotFoundError } from "./errors";
@@ -18,6 +20,10 @@ interface BranchList {
[branch: string]: Branch;
}
+interface SystemConfig {
+ tokenHashed: string;
+}
+
export interface HeadIdentity {
organization: string;
repository: string;
@@ -51,8 +57,6 @@ export interface EnvConfig {
hostDir: string;
/** The public directory for static files */
publicDir: string;
- /** The application server token to serve as user self-identifier */
- token: string;
/** Gradient setting 1 */
stage1: number;
/** Gradient setting 2 */
@@ -74,14 +78,21 @@ export const isError = (
* Handles data routing for application
*/
class Metadata {
- database: Db;
+ private dbClient: MongoClient;
+ private database: Db;
config: EnvConfig;
logger: winston.Logger;
- constructor(client: Db, data: EnvConfig) {
- this.logger = winston.createLogger(loggerConfig("META", data.logLevel));
- this.database = client;
+ constructor(client: MongoClient, data: EnvConfig) {
+ this.dbClient = client;
+ this.database = client.db(data.dbName);
this.config = data;
+ this.logger = winston.createLogger(loggerConfig("META", data.logLevel));
+ }
+
+ async close(): Promise<void> {
+ await this.dbClient.close();
+ this.logger.info("Database client connection closed.");
}
/**
@@ -157,10 +168,60 @@ class Metadata {
}
/**
- * Retrieve the application token from configuration
+ * Check whether the provided token matches the hashed token
*/
- getToken(): string {
- return this.config.token;
+ async checkToken(token: string): Promise<boolean> {
+ const result = await this.database
+ .collection<SystemConfig>("sysconfig")
+ .findOne({});
+
+ if (result !== null) {
+ return bcrypt.compare(token, result.tokenHashed);
+ } else {
+ return Promise.reject(Error("No system configuration in place"));
+ }
+ }
+
+ /**
+ * Generate a token for use as the user self-identifier.
+ *
+ * If the token is passed after it already exists, it will be overwritten.
+ */
+ async initializeToken(token?: string | undefined): Promise<boolean> {
+ const config = await this.database
+ .collection<SystemConfig>("sysconfig")
+ .countDocuments();
+
+ if (config > 0 && token === undefined) {
+ return true;
+ }
+
+ const useToken =
+ token === undefined
+ ? (() => {
+ const newToken = uuid();
+
+ this.logger.warn(
+ "TOKEN variable not provided, using this value instead: %s",
+ newToken
+ );
+ this.logger.warn(
+ "Use this provided token to push your coverage reports to the server."
+ );
+
+ return newToken;
+ })()
+ : token;
+
+ const sysconfig = {
+ tokenHashed: await bcrypt.hash(useToken, 10),
+ };
+
+ const result = await this.database
+ .collection<SystemConfig>("sysconfig")
+ .findOneAndReplace({}, sysconfig, { upsert: true });
+
+ return result.ok === 1;
}
/**
diff --git a/src/routes.test.ts b/src/routes.test.ts
index f151d79..188cd13 100644
--- a/src/routes.test.ts
+++ b/src/routes.test.ts
@@ -24,7 +24,6 @@ jest.mock("./util/logger", () => ({
import loggerConfig from "./util/logger";
import dotenv from "dotenv";
-import { Db } from "mongodb";
dotenv.config();
const LOGGER = winston.createLogger(loggerConfig("TEST", "debug"));
@@ -33,7 +32,7 @@ const HOST_DIR =
(() => {
const dir = path.join(__dirname, "..", "dist");
console.warn(
- `WARNING: HOST_DIR is not set - this is used to query files in src/routes.test.ts. Using '${dir}' as default HOST_DIR.`
+ `HOST_DIR is not set - this is used to query files in src/routes.test.ts. Using '${dir}' as default HOST_DIR.`
);
return dir;
})();
@@ -41,7 +40,6 @@ const TARGET_URL = "https://localhost:3000";
const TOKEN = "THISISCORRECT";
const config: EnvConfig = {
- token: TOKEN,
// should be just larger than the example report used
uploadLimit: Number(40000),
hostDir: HOST_DIR,
@@ -58,9 +56,7 @@ const config: EnvConfig = {
const defaultMetadata = {
logger: LOGGER,
- database: {} as Db,
config: config,
- getToken: () => config.token,
getUploadLimit: () => config.uploadLimit,
getHostDir: () => config.hostDir,
getPublicDir: () => config.publicDir,
@@ -79,11 +75,14 @@ const defaultMetadata = {
),
updateBranch: jest.fn(() => Promise.resolve(true)),
createRepository: jest.fn(() => Promise.resolve(true)),
+ checkToken: (token: string) => Promise.resolve(token === TOKEN),
+ close: jest.fn(),
};
const request = async (): Promise<SuperTest<Test>> => {
const app = express();
- app.use(routes(defaultMetadata as Metadata));
+ // The unknown is dbClient, since it's a private member. It's not used anyways for routes
+ app.use(routes(defaultMetadata as unknown as Metadata));
return _request(app);
};
diff --git a/src/routes.ts b