From 6633fb82b8282c721ea228df2ccb7f7750c26fa5 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 13 Jun 2023 14:24:08 -0500 Subject: [PATCH 1/4] better error handling --- package-lock.json | 62 ++++++++++++++++++++++------- package.json | 6 ++- src/clients/console-client/local.ts | 12 +++--- src/clients/console-client/s3.ts | 6 +-- src/middleware/error.ts | 10 ++--- src/utils/yaml.ts | 45 +++++++++++++++++++++ 6 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 src/utils/yaml.ts diff --git a/package-lock.json b/package-lock.json index 37f096e..faa40d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "@aws-sdk/client-api-gateway": "^3.267.0", "@aws-sdk/client-s3": "^3.288.0", "@aws-sdk/credential-providers": "^3.282.0", - "@tinystacks/ops-core": "^0.3.2", - "@tinystacks/ops-model": "^0.4.0", + "@tinystacks/ops-core": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", + "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "@types/react": "^18.0.28", "body-parser": "^1.20.1", "cached": "^6.1.0", @@ -22,6 +22,7 @@ "express": "^4.18.2", "express-openapi": "^12.1.0", "http-errors": "^2.0.0", + "http-status-codes": "^2.2.0", "js-yaml": "^4.1.0", "json-refs": "^3.0.15", "lodash.camelcase": "^4.3.0", @@ -42,6 +43,7 @@ "@types/cors": "^2.8.13", "@types/express": "^4.17.16", "@types/http-errors": "^2.0.1", + "@types/http-status-codes": "^1.2.0", "@types/jest": "^29.4.0", "@types/js-yaml": "^4.0.5", "@types/lodash.camelcase": "^4.3.7", @@ -4186,19 +4188,21 @@ }, "node_modules/@tinystacks/ops-core": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.3.2.tgz", - "integrity": "sha512-YTN2KHgtQgf3srcn8yH87kmvRqr/W5DSEWsi6fKFy+IoSd7/Mpv7QEijwadgNvlqEPbE9gixqERxsDatKcSCgA==", + "resolved": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", + "integrity": "sha512-HsyrrBaxAD59RzmEV31D2a4gBPZqu//d9l7hOnt3u3LDOX3RoxQ6a/aqZ/33efITuyYzN0EZq0V4OCk2vZsQzA==", "dependencies": { - "@tinystacks/ops-model": "^0.4.0", + "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "@types/react": "^18.0.28", + "http-status-codes": "^2.2.0", "lodash.get": "^4.4.2", "lodash.isnil": "^4.0.0" } }, "node_modules/@tinystacks/ops-model": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.4.0.tgz", - "integrity": "sha512-zh2BM/SLMdpH0ataWWmDzxgW/YGnRrBZ3f/4W/C6joFsNk4Yk7MMlY70Xfj8Z3kMLyauZ6ATK2GZbXbPP42z7w==", + "version": "0.4.1", + "resolved": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", + "integrity": "sha512-3K4iGz+8PaAkxhFdWUcU9Wg4ru1+P3tiV+IIL8cP1CynEIvyfkzrHsWAwp/5AdJi43gWxH+q893mQ+ah5xzGaA==", + "license": "ISC", "dependencies": { "openapi-typescript-codegen": "^0.23.0" } @@ -4319,6 +4323,16 @@ "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", "dev": true }, + "node_modules/@types/http-status-codes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/http-status-codes/-/http-status-codes-1.2.0.tgz", + "integrity": "sha512-vjpjevMaxtrtdrrV/TQNIFT7mKL8nvIKG7G/LjMDZdVvqRxRg5SNfGkeuSaowVc0rbK8xDA2d/Etunyb5GyzzA==", + "deprecated": "This is a stub types definition for http-status-codes (https://github.com/prettymuchbryce/node-http-status). http-status-codes provides its own type definitions, so you don\\'t need @types/http-status-codes installed!", + "dev": true, + "dependencies": { + "http-status-codes": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -7556,6 +7570,11 @@ "node": ">= 0.8" } }, + "node_modules/http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -15546,20 +15565,19 @@ } }, "@tinystacks/ops-core": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@tinystacks/ops-core/-/ops-core-0.3.2.tgz", - "integrity": "sha512-YTN2KHgtQgf3srcn8yH87kmvRqr/W5DSEWsi6fKFy+IoSd7/Mpv7QEijwadgNvlqEPbE9gixqERxsDatKcSCgA==", + "version": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", + "integrity": "sha512-HsyrrBaxAD59RzmEV31D2a4gBPZqu//d9l7hOnt3u3LDOX3RoxQ6a/aqZ/33efITuyYzN0EZq0V4OCk2vZsQzA==", "requires": { - "@tinystacks/ops-model": "^0.4.0", + "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "@types/react": "^18.0.28", + "http-status-codes": "^2.2.0", "lodash.get": "^4.4.2", "lodash.isnil": "^4.0.0" } }, "@tinystacks/ops-model": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@tinystacks/ops-model/-/ops-model-0.4.0.tgz", - "integrity": "sha512-zh2BM/SLMdpH0ataWWmDzxgW/YGnRrBZ3f/4W/C6joFsNk4Yk7MMlY70Xfj8Z3kMLyauZ6ATK2GZbXbPP42z7w==", + "version": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", + "integrity": "sha512-3K4iGz+8PaAkxhFdWUcU9Wg4ru1+P3tiV+IIL8cP1CynEIvyfkzrHsWAwp/5AdJi43gWxH+q893mQ+ah5xzGaA==", "requires": { "openapi-typescript-codegen": "^0.23.0" } @@ -15680,6 +15698,15 @@ "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", "dev": true }, + "@types/http-status-codes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/http-status-codes/-/http-status-codes-1.2.0.tgz", + "integrity": "sha512-vjpjevMaxtrtdrrV/TQNIFT7mKL8nvIKG7G/LjMDZdVvqRxRg5SNfGkeuSaowVc0rbK8xDA2d/Etunyb5GyzzA==", + "dev": true, + "requires": { + "http-status-codes": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -18157,6 +18184,11 @@ "toidentifier": "1.0.1" } }, + "http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", diff --git a/package.json b/package.json index 6e1fce7..f4b9bfb 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@types/cors": "^2.8.13", "@types/express": "^4.17.16", "@types/http-errors": "^2.0.1", + "@types/http-status-codes": "^1.2.0", "@types/jest": "^29.4.0", "@types/js-yaml": "^4.0.5", "@types/lodash.camelcase": "^4.3.7", @@ -69,8 +70,8 @@ "@aws-sdk/client-api-gateway": "^3.267.0", "@aws-sdk/client-s3": "^3.288.0", "@aws-sdk/credential-providers": "^3.282.0", - "@tinystacks/ops-core": "^0.3.2", - "@tinystacks/ops-model": "^0.4.0", + "@tinystacks/ops-core": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", + "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "@types/react": "^18.0.28", "body-parser": "^1.20.1", "cached": "^6.1.0", @@ -80,6 +81,7 @@ "express": "^4.18.2", "express-openapi": "^12.1.0", "http-errors": "^2.0.0", + "http-status-codes": "^2.2.0", "js-yaml": "^4.1.0", "json-refs": "^3.0.15", "lodash.camelcase": "^4.3.0", diff --git a/src/clients/console-client/local.ts b/src/clients/console-client/local.ts index 72c5e5a..7eed787 100644 --- a/src/clients/console-client/local.ts +++ b/src/clients/console-client/local.ts @@ -1,7 +1,6 @@ -import yaml from 'js-yaml'; import isNil from 'lodash.isnil'; import { ConsoleParser } from '@tinystacks/ops-core'; -import { Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; +import { Config, Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; import HttpError from 'http-errors'; import { writeFileSync @@ -11,6 +10,7 @@ import { } from 'path'; import FsUtils from '../../utils/fs-utils.js'; import IConsoleClient from './i-console-client.js'; +import Yaml from '../../utils/yaml.js'; class LocalConsoleClient implements IConsoleClient { async getConsole (_consoleName?: string): Promise { @@ -20,10 +20,10 @@ class LocalConsoleClient implements IConsoleClient { // console.debug('configFilePath: ', configFilePath); const configFile = FsUtils.tryToReadFile(configFilePath); if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); - const configJson = (yaml.load(configFile.toString()) as any)?.Console as YamlConsole; + const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); - if (!isNil(configJson)) { - const consoleType: ConsoleType = ConsoleParser.parse(configJson); + if (!isNil(configJson.Console)) { + const consoleType: ConsoleType = ConsoleParser.parse(configJson.Console as YamlConsole); return ConsoleParser.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!'); @@ -41,7 +41,7 @@ class LocalConsoleClient implements IConsoleClient { const previousConsole = await this.getConsole(consoleName); console.providers = previousConsole.providers; const yamlConsole = await console.toYaml(); - const consoleYml = yaml.dump({ Console: yamlConsole }); + const consoleYml = Yaml.stringify({ Console: yamlConsole }); const configPath = process.env.CONFIG_PATH; if (isNil(configPath)) throw HttpError.InternalServerError(`Cannot save console ${console.name}! No value was found for CONFIG_PATH!`); try { diff --git a/src/clients/console-client/s3.ts b/src/clients/console-client/s3.ts index e39f165..3d34b97 100644 --- a/src/clients/console-client/s3.ts +++ b/src/clients/console-client/s3.ts @@ -1,4 +1,3 @@ -import yaml from 'js-yaml'; import isNil from 'lodash.isnil'; import { ConsoleParser } from '@tinystacks/ops-core'; import { Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; @@ -12,6 +11,7 @@ import { S3 } from '@aws-sdk/client-s3'; import FsUtils from '../../utils/fs-utils.js'; import IConsoleClient from './i-console-client.js'; import { TMP_DIR } from '../../constants.js'; +import Yaml from '../../utils/yaml.js'; type S3Info = { bucketName: string; @@ -177,7 +177,7 @@ class S3ConsoleClient implements IConsoleClient { */ const configFile = await this.getConfig(); if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); - const configJson = (yaml.load(configFile.toString()) as any)?.Console as YamlConsole; + const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); if (!isNil(configJson)) { const consoleType: ConsoleType = ConsoleParser.parse(configJson); @@ -200,7 +200,7 @@ class S3ConsoleClient implements IConsoleClient { const previousConsole = await this.getConsole(consoleName); console.providers = previousConsole.providers; const yamlConsole = await console.toYaml(); - const consoleYml = yaml.dump({ Console: yamlConsole }); + const consoleYml = Yaml.stringify({ Console: yamlConsole }); const configPath = process.env.CONFIG_PATH; if (isNil(configPath)) throw HttpError.InternalServerError(`Cannot save console ${console.name}! No value was found for CONFIG_PATH!`); try { diff --git a/src/middleware/error.ts b/src/middleware/error.ts index e20261b..349d379 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -2,11 +2,11 @@ import HttpError from 'http-errors'; import { Request, Response, NextFunction } from 'express'; import { TinyStacksError } from '@tinystacks/ops-core'; -export default async function errorMiddleware (error: unknown, request: Request, response: Response, next: NextFunction) { - console.error(error); - if (TinyStacksError.isTinyStacksError(error) || HttpError.isHttpError(error)) { - const { status, message } = error as TinyStacksError | HttpError.HttpError; - response.status(status).json({ status, message }); +export default async function errorMiddleware (e: unknown, _request: Request, response: Response, next: NextFunction) { + console.error(e); + if (TinyStacksError.isTinyStacksError(e) || HttpError.isHttpError(e)) { + const error = TinyStacksError.fromJson(e as any); + response.status(error.status).json(error); } else { const ise = HttpError.InternalServerError('An unexpected error occured! See the API logs for more details.'); response.status(ise.status).json(ise); diff --git a/src/utils/yaml.ts b/src/utils/yaml.ts new file mode 100644 index 0000000..9bc49c9 --- /dev/null +++ b/src/utils/yaml.ts @@ -0,0 +1,45 @@ +import jsYaml, { YAMLException } from 'js-yaml'; +import { TinyStacksError } from '@tinystacks/ops-core'; +import { StatusCodes } from 'http-status-codes'; + +class Yaml { + static handleError (e: any, message: string): never { + const tsError = TinyStacksError.fromJson({ message, status: StatusCodes.UNPROCESSABLE_ENTITY }); + if (e.name === 'YAMLException') { + const error = e as YAMLException; + tsError.cause = error?.reason?.trim(); + tsError.context = error?.mark?.snippet?.split('\n')?.map(s => s.trim())?.join('\n'); + } + throw tsError; + } + + static parseAs (yaml: string): T { + try { + return jsYaml.load(yaml) as T; + } catch (e) { + this.handleError(e, 'Failed to parse yaml!'); + } + } + + static parse (yaml: string): any { + try { + return jsYaml.load(yaml) as any; + } catch (e) { + this.handleError(e, 'Failed to parse yaml!'); + } + } + + static stringify (json: any): string { + try { + return jsYaml.dump(json); + } catch (e) { + this.handleError(e, 'Failed to stringify object to yaml!'); + } + } +} + +export { + Yaml +}; + +export default Yaml; \ No newline at end of file From 0898f10024210505e3a0a8012660eaf9b9367bdc Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Wed, 14 Jun 2023 15:50:36 -0500 Subject: [PATCH 2/4] handle split widgets --- package-lock.json | 6 ++-- .../console-client/i-console-client.ts | 10 +++--- src/clients/console-client/index.ts | 13 +++----- src/clients/console-client/local.ts | 14 ++++---- src/clients/console-client/s3.ts | 14 ++++---- src/clients/dashboard-client.ts | 20 +++++------ src/clients/widget-client.ts | 33 ++++++++++--------- src/controllers/console-controller.ts | 8 ++--- src/controllers/dashboard-controller.ts | 8 ++--- src/types/index.ts | 20 +++++------ src/utils/parsing-utils.ts | 4 +-- tsconfig.json | 2 +- 12 files changed, 75 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index faa40d6..85973ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4189,10 +4189,9 @@ "node_modules/@tinystacks/ops-core": { "version": "0.3.2", "resolved": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", - "integrity": "sha512-HsyrrBaxAD59RzmEV31D2a4gBPZqu//d9l7hOnt3u3LDOX3RoxQ6a/aqZ/33efITuyYzN0EZq0V4OCk2vZsQzA==", + "integrity": "sha512-nkRgx8+qc4ZiWE/8+nPOzD7131wDWTEfJjlBtpiSjUKDvmN4WMgiGv/lC/TooqQYq4sxC3Q3siMpFnQ7KaQj/Q==", "dependencies": { "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", - "@types/react": "^18.0.28", "http-status-codes": "^2.2.0", "lodash.get": "^4.4.2", "lodash.isnil": "^4.0.0" @@ -15566,10 +15565,9 @@ }, "@tinystacks/ops-core": { "version": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", - "integrity": "sha512-HsyrrBaxAD59RzmEV31D2a4gBPZqu//d9l7hOnt3u3LDOX3RoxQ6a/aqZ/33efITuyYzN0EZq0V4OCk2vZsQzA==", + "integrity": "sha512-nkRgx8+qc4ZiWE/8+nPOzD7131wDWTEfJjlBtpiSjUKDvmN4WMgiGv/lC/TooqQYq4sxC3Q3siMpFnQ7KaQj/Q==", "requires": { "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", - "@types/react": "^18.0.28", "http-status-codes": "^2.2.0", "lodash.get": "^4.4.2", "lodash.isnil": "^4.0.0" diff --git a/src/clients/console-client/i-console-client.ts b/src/clients/console-client/i-console-client.ts index ae1207a..d71b221 100644 --- a/src/clients/console-client/i-console-client.ts +++ b/src/clients/console-client/i-console-client.ts @@ -1,10 +1,10 @@ -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; interface IConsoleClient { - getConsoles (): Promise; - getConsole (consoleName: string): Promise; - saveConsole (consoleName: string, console: ConsoleParser): Promise; - deleteConsole (consoleName: string): Promise; + getConsoles (): Promise; + getConsole (consoleName: string): Promise; + saveConsole (consoleName: string, console: Console): Promise; + deleteConsole (consoleName: string): Promise; } export { diff --git a/src/clients/console-client/index.ts b/src/clients/console-client/index.ts index 3fe4120..4dc5861 100644 --- a/src/clients/console-client/index.ts +++ b/src/clients/console-client/index.ts @@ -1,17 +1,14 @@ -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; import IConsoleClient from './i-console-client.js'; import { LocalConsoleClient } from './local.js'; import { S3ConsoleClient } from './s3.js'; -/** - * TODO: Eventually this becomes a proxy class which based on the environment returns a specific client i.e. local vs github vs s3 etc. - */ class ConsoleClient implements IConsoleClient { client: IConsoleClient; - getConsoles: () => Promise; - getConsole: (consoleName: string) => Promise; - saveConsole: (consoleName: string, console: ConsoleParser) => Promise; - deleteConsole: (_consoleName: string) => Promise; + getConsoles: () => Promise; + getConsole: (consoleName: string) => Promise; + saveConsole: (consoleName: string, console: Console) => Promise; + deleteConsole: (_consoleName: string) => Promise; constructor () { const configPath = process.env.CONFIG_PATH; diff --git a/src/clients/console-client/local.ts b/src/clients/console-client/local.ts index 7eed787..8fdf958 100644 --- a/src/clients/console-client/local.ts +++ b/src/clients/console-client/local.ts @@ -1,5 +1,5 @@ import isNil from 'lodash.isnil'; -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; import { Config, Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; import HttpError from 'http-errors'; import { @@ -13,7 +13,7 @@ import IConsoleClient from './i-console-client.js'; import Yaml from '../../utils/yaml.js'; class LocalConsoleClient implements IConsoleClient { - async getConsole (_consoleName?: string): Promise { + async getConsole (_consoleName?: string): Promise { const configPath = process.env.CONFIG_PATH; if (configPath) { const configFilePath = resolvePath(configPath); @@ -23,20 +23,20 @@ class LocalConsoleClient implements IConsoleClient { const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); if (!isNil(configJson.Console)) { - const consoleType: ConsoleType = ConsoleParser.parse(configJson.Console as YamlConsole); - return ConsoleParser.fromJson(consoleType); + const consoleType: ConsoleType = Console.parse(configJson.Console as YamlConsole); + return Console.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!'); } throw HttpError.InternalServerError('Cannot fetch console! No value was found for CONFIG_PATH!'); } - async getConsoles (): Promise { + async getConsoles (): Promise { const consoles = []; const console = await this.getConsole(); if (console) consoles.push(console); return consoles; } - async saveConsole (consoleName: string, console: ConsoleParser): Promise { + async saveConsole (consoleName: string, console: Console): Promise { console.name = consoleName; const previousConsole = await this.getConsole(consoleName); console.providers = previousConsole.providers; @@ -53,7 +53,7 @@ class LocalConsoleClient implements IConsoleClient { throw error; } } - async deleteConsole (consoleName: string): Promise { + async deleteConsole (consoleName: string): Promise { const configPath = process.env.CONFIG_PATH; if (isNil(configPath)) throw HttpError.InternalServerError(`Cannot delete console ${consoleName}! No value was found for CONFIG_PATH!`); try { diff --git a/src/clients/console-client/s3.ts b/src/clients/console-client/s3.ts index 3d34b97..26b476b 100644 --- a/src/clients/console-client/s3.ts +++ b/src/clients/console-client/s3.ts @@ -1,5 +1,5 @@ import isNil from 'lodash.isnil'; -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; import { Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; import HttpError from 'http-errors'; import { @@ -167,7 +167,7 @@ class S3ConsoleClient implements IConsoleClient { } } - async getConsole (_consoleName?: string): Promise { + async getConsole (_consoleName?: string): Promise { const configPath = process.env.CONFIG_PATH; if (configPath) { /** @@ -180,22 +180,22 @@ class S3ConsoleClient implements IConsoleClient { const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); if (!isNil(configJson)) { - const consoleType: ConsoleType = ConsoleParser.parse(configJson); - return ConsoleParser.fromJson(consoleType); + const consoleType: ConsoleType = Console.parse(configJson); + return Console.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!'); } throw HttpError.InternalServerError('Cannot fetch console! No value was found for CONFIG_PATH!'); } - async getConsoles (): Promise { + async getConsoles (): Promise { const consoles = []; const console = await this.getConsole(); if (console) consoles.push(console); return consoles; } - async saveConsole (consoleName: string, console: ConsoleParser): Promise { + async saveConsole (consoleName: string, console: Console): Promise { console.name = consoleName; const previousConsole = await this.getConsole(consoleName); console.providers = previousConsole.providers; @@ -213,7 +213,7 @@ class S3ConsoleClient implements IConsoleClient { } } - async deleteConsole (consoleName: string): Promise { + async deleteConsole (consoleName: string): Promise { const configPath = process.env.CONFIG_PATH; if (isNil(configPath)) throw HttpError.InternalServerError(`Cannot delete console ${consoleName}! No value was found for CONFIG_PATH!`); try { diff --git a/src/clients/dashboard-client.ts b/src/clients/dashboard-client.ts index 7094b7a..27e296a 100644 --- a/src/clients/dashboard-client.ts +++ b/src/clients/dashboard-client.ts @@ -3,8 +3,8 @@ import HttpError from 'http-errors'; import ConsoleClient from './console-client/index.js'; import upperFirst from 'lodash.upperfirst'; import camelCase from 'lodash.camelcase'; -import { Dashboard } from '@tinystacks/ops-model'; -import { ConsoleParser, DashboardParser } from '@tinystacks/ops-core'; +import { Dashboard as DashboardType } from '@tinystacks/ops-model'; +import { Console, Dashboard } from '@tinystacks/ops-core'; const DashboardClient = { handleError (error: unknown): never { @@ -15,18 +15,18 @@ const DashboardClient = { } throw error; }, - async getDashboard (consoleName: string, dashboardId: string): Promise { //should return a dashboardParser + async getDashboard (consoleName: string, dashboardId: string): Promise { //should return a dashboardParser try { const consoleClient = new ConsoleClient(); - const console : ConsoleParser = await consoleClient.getConsole(consoleName); + const console : Console = await consoleClient.getConsole(consoleName); const existingDashboard = console.dashboards[dashboardId]; - if (!existingDashboard) throw HttpError.NotFound(`Dashboard with id ${dashboardId} does not exist in console ${consoleName}!`); + if (!existingDashboard) throw HttpError.NotFound(`DashboardType with id ${dashboardId} does not exist in console ${consoleName}!`); return existingDashboard; } catch (error) { return this.handleError(error); } }, - async getDashboards (consoleName: string): Promise { + async getDashboards (consoleName: string): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); @@ -35,7 +35,7 @@ const DashboardClient = { return this.handleError(error); } }, - async createDashboard (consoleName: string, dashboard: Dashboard): Promise { + async createDashboard (consoleName: string, dashboard: DashboardType): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); @@ -53,10 +53,10 @@ const DashboardClient = { return this.handleError(error); } }, - async updateDashboard (consoleName: string, dashboardId: string, dashboard: Dashboard): Promise { + async updateDashboard (consoleName: string, dashboardId: string, dashboard: DashboardType): Promise { try { const consoleClient = new ConsoleClient(); - const console: ConsoleParser = await consoleClient.getConsole(consoleName); + const console: Console = await consoleClient.getConsole(consoleName); const existingDashboard = console.dashboards[dashboardId]; if (isNil(existingDashboard)) throw HttpError.NotFound(`Cannot update dashboard with id ${dashboardId} because this dashboard does not exist on console ${consoleName}!`); // No trickery allowed. @@ -68,7 +68,7 @@ const DashboardClient = { return this.handleError(error); } }, - async deleteDashboard (consoleName: string, dashboardId: string): Promise { + async deleteDashboard (consoleName: string, dashboardId: string): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); diff --git a/src/clients/widget-client.ts b/src/clients/widget-client.ts index 1435aa5..0a248b0 100644 --- a/src/clients/widget-client.ts +++ b/src/clients/widget-client.ts @@ -4,8 +4,8 @@ import ConsoleClient from './console-client/index.js'; import upperFirst from 'lodash.upperfirst'; import camelCase from 'lodash.camelcase'; import get from 'lodash.get'; -import { Widget } from '@tinystacks/ops-model'; -import { BaseWidget } from '@tinystacks/ops-core'; +import { Widget as WidgetType } from '@tinystacks/ops-model'; +import { Widget as WidgetController, Models } from '@tinystacks/ops-core'; import { GetWidgetArguments, HydrateWidgetReferencesArguments, @@ -13,6 +13,8 @@ import { } from '../types/index.js'; import { castParametersToDeclaredTypes, castToType } from '../utils/parsing-utils.js'; +import WidgetModel = Models.Widget; + // TODO: should we make this a class that implement a WidgetClient interface? const WidgetClient = { handleError (error: unknown): never { @@ -23,7 +25,7 @@ const WidgetClient = { } throw error; }, - async getWidget (args: GetWidgetArguments): Promise { + async getWidget (args: GetWidgetArguments): Promise { try { const { consoleName, @@ -34,9 +36,10 @@ const WidgetClient = { } = args; const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); - const widget: BaseWidget = console.widgets[widgetId]; - if (isNil(widget)) throw HttpError.NotFound(`Widget with id ${widgetId} does not exist on console ${consoleName}!`); - const { widgets, providers, dashboards = {}, constants = {} } = console; + const widget: WidgetController = console.widgets[widgetId] as WidgetController; + if (isNil(widget)) throw HttpError.NotFound(`WidgetType with id ${widgetId} does not exist on console ${consoleName}!`); + const { widgets: ws, providers, dashboards = {}, constants = {} } = console; + const widgets = ws as { [id: string]: WidgetController }; const typeCastParameters = castParametersToDeclaredTypes(widgetId, parameters, dashboards, dashboardId); const hydratedWidget = await this.hydrateWidgetReferences({ widget, @@ -53,7 +56,7 @@ const WidgetClient = { return this.handleError(error); } }, - async createWidget (consoleName: string, widget: Widget): Promise { + async createWidget (consoleName: string, widget: WidgetType): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); @@ -63,7 +66,7 @@ const WidgetClient = { if (existingWidget) throw HttpError.Conflict(`Cannot create new widget with id ${widget.id} because a widget with this id already exists on console ${consoleName}!`); // Filter out any junk from the request const widgetDependencySource = console.dependencies[widget.type]; - const widgetInstance = await BaseWidget.fromJson(widget, widgetDependencySource); + const widgetInstance = await WidgetModel.fromJson(widget, widgetDependencySource); const newWidget = widgetInstance.toJson(); await console.addWidget(newWidget, widgetId); await consoleClient.saveConsole(consoleName, console); @@ -72,7 +75,7 @@ const WidgetClient = { return this.handleError(error); } }, - async updateWidget (consoleName: string, widgetId: string, widget: Widget): Promise { + async updateWidget (consoleName: string, widgetId: string, widget: WidgetType): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); @@ -82,7 +85,7 @@ const WidgetClient = { widget.id = widgetId; // Filter out any junk from the request const widgetDependencySource = console.dependencies[widget.type]; - const widgetInstance = await BaseWidget.fromJson(widget, widgetDependencySource); + const widgetInstance = await WidgetModel.fromJson(widget, widgetDependencySource); const updatedWidget = widgetInstance.toJson(); await console.updateWidget(updatedWidget, widgetId); await consoleClient.saveConsole(console.name, console); @@ -91,11 +94,11 @@ const WidgetClient = { return this.handleError(error); } }, - async deleteWidget (consoleName: string, widgetId: string): Promise { + async deleteWidget (consoleName: string, widgetId: string): Promise { try { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); - const existingWidget = console.widgets[widgetId]; + const existingWidget = console.widgets[widgetId] as WidgetController; if (isNil(existingWidget)) throw HttpError.NotFound(`Cannot delete widget with id ${widgetId} because this widget does not exist on console ${consoleName}!`); console.deleteWidget(widgetId); await consoleClient.saveConsole(console.name, console); @@ -108,7 +111,7 @@ const WidgetClient = { // recursively for every ref: // recA // resolve ref by calling getData - async hydrateWidgetReferences (args: HydrateWidgetReferencesArguments) { + async hydrateWidgetReferences (args: HydrateWidgetReferencesArguments): Promise { const { widget, widgets, @@ -119,7 +122,7 @@ const WidgetClient = { dashboards, dashboardId } = args; - const referencedWidgets: Record = {}; + const referencedWidgets: Record = {}; const resolvedWidget = await this.resolveWidgetPropertyReferences({ property: widget, widgets, @@ -132,7 +135,7 @@ const WidgetClient = { }); - const hydratedProviders = ((resolvedWidget as Widget).providerIds || []).map((providerId: string) => { + const hydratedProviders = ((resolvedWidget as WidgetType).providerIds || []).map((providerId: string) => { return providers[providerId]; }); await widget.getData(hydratedProviders, overrides, parameters); diff --git a/src/controllers/console-controller.ts b/src/controllers/console-controller.ts index f9eadbb..875522b 100644 --- a/src/controllers/console-controller.ts +++ b/src/controllers/console-controller.ts @@ -1,24 +1,24 @@ import { Console as ConsoleType } from '@tinystacks/ops-model'; +import { Console } from '@tinystacks/ops-core'; import ConsoleClient from '../clients/console-client/index.js'; -import { ConsoleParser } from '@tinystacks/ops-core'; const ConsoleController = { async getConsoles (): Promise { const consoleClient = new ConsoleClient(); - const consoles: ConsoleParser[] = await consoleClient.getConsoles(); + const consoles: Console[] = await consoleClient.getConsoles(); const consoleTypes: ConsoleType[] = consoles.map( (console) => { return console.toJson(); }); return consoleTypes; }, async postConsole (createConsoleBody: ConsoleType): Promise { - const console: ConsoleParser = await ConsoleParser.fromJson(createConsoleBody); + const console: Console = await Console.fromJson(createConsoleBody); const consoleClient = new ConsoleClient(); const newConsole = await consoleClient.saveConsole(console.name, console); return newConsole.toJson(); }, async putConsole (consoleName: string, updateConsoleBody: ConsoleType): Promise { - const console: ConsoleParser = await ConsoleParser.fromJson(updateConsoleBody); + const console: Console = await Console.fromJson(updateConsoleBody); const consoleClient = new ConsoleClient(); const updatedConsole = await consoleClient.saveConsole(consoleName, console); return updatedConsole.toJson(); diff --git a/src/controllers/dashboard-controller.ts b/src/controllers/dashboard-controller.ts index 3f79995..f005b73 100644 --- a/src/controllers/dashboard-controller.ts +++ b/src/controllers/dashboard-controller.ts @@ -1,21 +1,21 @@ import { Dashboard as DashboardType } from '@tinystacks/ops-model'; +import { Dashboard } from '@tinystacks/ops-core'; import DashboardClient from '../clients/dashboard-client.js'; -import { DashboardParser } from '@tinystacks/ops-core'; const DashboardController = { async getDashboards (consoleName: string): Promise { - const dashboards : DashboardParser[] = await DashboardClient.getDashboards(consoleName); + const dashboards : Dashboard[] = await DashboardClient.getDashboards(consoleName); const dashboardTypes: DashboardType[] = dashboards.map( (dashboard) => { return dashboard.toJson(); }); return dashboardTypes; }, async postDashboard (consoleName: string, createDashboardBody: DashboardType): Promise { - const dashboard: DashboardParser = DashboardParser.fromJson(createDashboardBody); + const dashboard: Dashboard = Dashboard.fromJson(createDashboardBody); return ( await DashboardClient.createDashboard(consoleName, dashboard)).toJson(); }, async putDashboard (consoleName: string, dashboardId: string, updateDashboardBody: DashboardType): Promise { - const dashboard: DashboardParser = DashboardParser.fromJson(updateDashboardBody); + const dashboard: Dashboard = Dashboard.fromJson(updateDashboardBody); return (await DashboardClient.updateDashboard(consoleName, dashboardId, dashboard)).toJson(); }, async deleteDashboard (consoleName: string, dashboardId: string): Promise { diff --git a/src/types/index.ts b/src/types/index.ts index 8d1c709..3648a67 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ -import { BaseProvider, BaseWidget, DashboardParser } from '@tinystacks/ops-core'; -import { Constant, Widget } from '@tinystacks/ops-model'; +import { Widget, Provider, Dashboard } from '@tinystacks/ops-core'; +import { Constant, Widget as WidgetType } from '@tinystacks/ops-model'; type Json = { [key: string]: any @@ -14,24 +14,24 @@ type GetWidgetArguments = { }; type HydrateWidgetReferencesArguments = { - widget: BaseWidget; - widgets: Record; - providers: Record; + widget: Widget; + widgets: Record; + providers: Record; overrides?: Json; parameters?: Json; constants?: Record; - dashboards: Record; + dashboards: Record; dashboardId?: string; }; type ResolveWidgetPropertyReferencesArguments = { property: any; - widgets: Record; - providers: Record; - referencedWidgets: Record; + widgets: Record; + providers: Record; + referencedWidgets: Record; parameters?: Json; constants?: Record; - dashboards: Record; + dashboards: Record; dashboardId?: string; } diff --git a/src/utils/parsing-utils.ts b/src/utils/parsing-utils.ts index 5a0c9cd..3fe9e0b 100644 --- a/src/utils/parsing-utils.ts +++ b/src/utils/parsing-utils.ts @@ -1,5 +1,5 @@ import { Constant, Parameter } from '@tinystacks/ops-model'; -import { DashboardParser } from '@tinystacks/ops-core'; +import { Dashboard } from '@tinystacks/ops-core'; import { Json } from '../types/index.js'; import difference from 'lodash.difference'; @@ -60,7 +60,7 @@ function castToType (value: any, type: Constant.type | Parameter.type | string) } } -function castParametersToDeclaredTypes (widgetId: string, parameters: Json = {}, dashboards: Record = {}, dashboardId?: string): Json { +function castParametersToDeclaredTypes (widgetId: string, parameters: Json = {}, dashboards: Record = {}, dashboardId?: string): Json { const parameterKeys = Object.keys(parameters).sort(); const dashboardContext = dashboardId ? dashboards[dashboardId] : diff --git a/tsconfig.json b/tsconfig.json index 62400ad..db98e80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2022", "module": "ES2022", - "moduleResolution": "node", + "moduleResolution": "nodenext", "declaration": true, "strict": true, "noImplicitAny": true, From b82b8e26505409321324899973f3fd9ccd6b0075 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Thu, 15 Jun 2023 12:07:23 -0500 Subject: [PATCH 3/4] fix tests, bug fixes --- package-lock.json | 4 +- package.json | 2 +- src/clients/console-client/local.ts | 2 +- src/clients/console-client/s3.ts | 8 +-- src/clients/widget-client.ts | 2 +- test/clients/console-client/index.test.ts | 4 +- test/clients/console-client/local.test.ts | 26 +++++----- test/clients/console-client/s3.test.ts | 22 ++++----- test/clients/page-client.test.ts | 59 +++++++++++------------ test/clients/widget-client.test.ts | 28 +++++------ test/middleware/error.test.ts | 8 +-- test/utils/parsing-utils.test.ts | 4 +- 12 files changed, 84 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85973ad..2eb677d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4189,7 +4189,7 @@ "node_modules/@tinystacks/ops-core": { "version": "0.3.2", "resolved": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", - "integrity": "sha512-nkRgx8+qc4ZiWE/8+nPOzD7131wDWTEfJjlBtpiSjUKDvmN4WMgiGv/lC/TooqQYq4sxC3Q3siMpFnQ7KaQj/Q==", + "integrity": "sha512-bja0AspfskE4mO0eugbWzJeWkcXxSn5wvKlbLc2kfNRNd03qnLl2Gvqxbj89hN8uysjTLAywV7gr4tDxE8rNpg==", "dependencies": { "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "http-status-codes": "^2.2.0", @@ -15565,7 +15565,7 @@ }, "@tinystacks/ops-core": { "version": "file:../ops-core/tinystacks-ops-core-0.3.2.tgz", - "integrity": "sha512-nkRgx8+qc4ZiWE/8+nPOzD7131wDWTEfJjlBtpiSjUKDvmN4WMgiGv/lC/TooqQYq4sxC3Q3siMpFnQ7KaQj/Q==", + "integrity": "sha512-bja0AspfskE4mO0eugbWzJeWkcXxSn5wvKlbLc2kfNRNd03qnLl2Gvqxbj89hN8uysjTLAywV7gr4tDxE8rNpg==", "requires": { "@tinystacks/ops-model": "file:../ops-model/tinystacks-ops-model-0.4.1.tgz", "http-status-codes": "^2.2.0", diff --git a/package.json b/package.json index f4b9bfb..b78a007 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "start": "node ./dist/server.js", "test": "jest", "test-cov": "jest --coverage", - "test-file": "jest ./test/utils/parsing-utils.test.ts", + "test-file": "jest ./test/clients/console-client/local.test.ts", "view-test-cov": "jest --coverage || true && open coverage/lcov-report/index.html" }, "devDependencies": { diff --git a/src/clients/console-client/local.ts b/src/clients/console-client/local.ts index 8fdf958..46da300 100644 --- a/src/clients/console-client/local.ts +++ b/src/clients/console-client/local.ts @@ -22,7 +22,7 @@ class LocalConsoleClient implements IConsoleClient { if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); - if (!isNil(configJson.Console)) { + if (!isNil(configJson?.Console)) { const consoleType: ConsoleType = Console.parse(configJson.Console as YamlConsole); return Console.fromJson(consoleType); } diff --git a/src/clients/console-client/s3.ts b/src/clients/console-client/s3.ts index 26b476b..3eb6800 100644 --- a/src/clients/console-client/s3.ts +++ b/src/clients/console-client/s3.ts @@ -1,6 +1,6 @@ import isNil from 'lodash.isnil'; import { Console } from '@tinystacks/ops-core'; -import { Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; +import { Config, Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; import HttpError from 'http-errors'; import { mkdirSync, @@ -177,10 +177,10 @@ class S3ConsoleClient implements IConsoleClient { */ const configFile = await this.getConfig(); if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); - const configJson = Yaml.parseAs(configFile.toString()); + const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); - if (!isNil(configJson)) { - const consoleType: ConsoleType = Console.parse(configJson); + if (!isNil(configJson?.Console)) { + const consoleType: ConsoleType = Console.parse(configJson?.Console as YamlConsole); return Console.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!'); diff --git a/src/clients/widget-client.ts b/src/clients/widget-client.ts index 0a248b0..503b9dc 100644 --- a/src/clients/widget-client.ts +++ b/src/clients/widget-client.ts @@ -37,7 +37,7 @@ const WidgetClient = { const consoleClient = new ConsoleClient(); const console = await consoleClient.getConsole(consoleName); const widget: WidgetController = console.widgets[widgetId] as WidgetController; - if (isNil(widget)) throw HttpError.NotFound(`WidgetType with id ${widgetId} does not exist on console ${consoleName}!`); + if (isNil(widget)) throw HttpError.NotFound(`Widget with id ${widgetId} does not exist on console ${consoleName}!`); const { widgets: ws, providers, dashboards = {}, constants = {} } = console; const widgets = ws as { [id: string]: WidgetController }; const typeCastParameters = castParametersToDeclaredTypes(widgetId, parameters, dashboards, dashboardId); diff --git a/test/clients/console-client/index.test.ts b/test/clients/console-client/index.test.ts index b79b931..0e518df 100644 --- a/test/clients/console-client/index.test.ts +++ b/test/clients/console-client/index.test.ts @@ -12,11 +12,11 @@ jest.mock('../../../src/clients/console-client/local.js', () => ({ LocalConsoleClient: mockLocalClient })); -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; import ConsoleClient from '../../../src/clients/console-client/index.js'; async function getMockConsole () { - return await ConsoleParser.fromJson({ + return await Console.fromJson({ name: 'mock-console', dashboards: {}, providers: {}, diff --git a/test/clients/console-client/local.test.ts b/test/clients/console-client/local.test.ts index dc23179..77e90a8 100644 --- a/test/clients/console-client/local.test.ts +++ b/test/clients/console-client/local.test.ts @@ -23,13 +23,13 @@ jest.mock('fs', () => ({ const mockGetConsole = jest.fn(); -import { Console } from '@tinystacks/ops-model'; +import { Console as ConsoleType } from '@tinystacks/ops-model'; import LocalConsoleClient from '../../../src/clients/console-client/local'; import HttpError from 'http-errors'; -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; const mockConsoleName = 'mock-console'; -const mockConsoleJson: Console = { +const mockConsoleJson: ConsoleType = { name: mockConsoleName, constants: {}, dashboards: {}, @@ -86,7 +86,7 @@ describe('local console client tests', () => { expect(thrownError).toEqual(HttpError.NotFound('Cannot fetch console! Config file ./mock.yml not found!')); } }); - it('returns Console on success', async () => { + it('returns ConsoleType on success', async () => { const mockConfigPath = './mock.yml'; const mockConsole = { ...mockConsoleJson }; process.env.CONFIG_PATH = mockConfigPath; @@ -106,7 +106,7 @@ describe('local console client tests', () => { expect(mockLoad).toBeCalledWith('Console: '); expect(result).toEqual(mockConsole); }); - it('throws InternalServerError if yaml cannot be loaded', async () => { + it.only('throws InternalServerError if yaml cannot be loaded', async () => { const mockConfigPath = './mock.yml'; process.env.CONFIG_PATH = mockConfigPath; mockResolve.mockReturnValueOnce(mockConfigPath); @@ -131,7 +131,7 @@ describe('local console client tests', () => { }); describe('getConsoles', () => { it('returns array of console if one exists', async () => { - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(LocalConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); const result = await localConsoleClient.getConsoles(); @@ -150,7 +150,7 @@ describe('local console client tests', () => { }); describe('saveConsole', () => { it('throws InternalServerError if CONFIG_PATH is not set', async () => { - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(LocalConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); let thrownError; try { @@ -164,9 +164,9 @@ describe('local console client tests', () => { expect(thrownError).toEqual(HttpError.InternalServerError('Cannot save console mock-console! No value was found for CONFIG_PATH!')); } }); - it('writes to file and returns saved Console on success', async () => { + it('writes to file and returns saved ConsoleType on success', async () => { const mockConfigPath = './mock.yml'; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); process.env.CONFIG_PATH = mockConfigPath; mockResolve.mockReturnValueOnce(mockConfigPath); @@ -188,7 +188,7 @@ describe('local console client tests', () => { }); it('logs and re-throws errors', async () => { const mockConfigPath = './mock.yml'; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(LocalConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); const mockError = new Error(); process.env.CONFIG_PATH = mockConfigPath; @@ -225,9 +225,9 @@ describe('local console client tests', () => { expect(thrownError).toEqual(HttpError.InternalServerError('Cannot delete console mock-console! No value was found for CONFIG_PATH!')); } }); - it('overwrites config file with empty string and returns previous Console state on success', async () => { + it('overwrites config file with empty string and returns previous ConsoleType state on success', async () => { const mockConfigPath = './mock.yml'; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); process.env.CONFIG_PATH = mockConfigPath; mockResolve.mockReturnValueOnce(mockConfigPath); jest.spyOn(LocalConsoleClient.prototype, 'getConsole').mockResolvedValueOnce(mockConsole); @@ -243,7 +243,7 @@ describe('local console client tests', () => { }); it('logs and re-throws errors', async () => { const mockConfigPath = './mock.yml'; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); const mockError = new Error(); process.env.CONFIG_PATH = mockConfigPath; mockWriteFileSync.mockImplementationOnce(() => { throw mockError; }); diff --git a/test/clients/console-client/s3.test.ts b/test/clients/console-client/s3.test.ts index b2be198..32207cc 100644 --- a/test/clients/console-client/s3.test.ts +++ b/test/clients/console-client/s3.test.ts @@ -31,13 +31,13 @@ jest.mock('@aws-sdk/client-s3', () => ({ const mockGetConsole = jest.fn(); -import { Console } from '@tinystacks/ops-model'; +import { Console as ConsoleType } from '@tinystacks/ops-model'; import S3ConsoleClient from '../../../src/clients/console-client/s3'; import HttpError from 'http-errors'; -import { ConsoleParser } from '@tinystacks/ops-core'; +import { Console } from '@tinystacks/ops-core'; const mockConsoleName = 'mock-console'; -const mockConsoleJson: Console = { +const mockConsoleJson: ConsoleType = { name: mockConsoleName, constants: {}, dashboards: {}, @@ -230,7 +230,7 @@ describe('local console client tests', () => { }); describe('getConsoles', () => { it('returns array of console if one exists', async () => { - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(S3ConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); const result = await s3ConsoleClient.getConsoles(); @@ -250,7 +250,7 @@ describe('local console client tests', () => { }); describe('saveConsole', () => { it('throws InternalServerError if CONFIG_PATH is not set', async () => { - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(S3ConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); let thrownError; try { @@ -263,9 +263,9 @@ describe('local console client tests', () => { expect(thrownError).toEqual(HttpError.InternalServerError('Cannot save console mock-console! No value was found for CONFIG_PATH!')); } }); - it('writes to file and returns saved Console on success', async () => { + it('writes to file and returns saved ConsoleType on success', async () => { const mockConfigPath = `s3://${mockConfigBucket}/${mockUser}/${mockConfigFileName}`; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); process.env.CONFIG_PATH = mockConfigPath; mockDump.mockReturnValueOnce(mockConfigYaml); @@ -299,7 +299,7 @@ describe('local console client tests', () => { }); it('logs and re-throws errors', async () => { const mockConfigPath = `s3://${mockConfigBucket}/${mockUser}/${mockConfigFileName}`; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); jest.spyOn(S3ConsoleClient.prototype, 'getConsole').mockResolvedValue(mockConsole); const mockError = new Error(); process.env.CONFIG_PATH = mockConfigPath; @@ -338,7 +338,7 @@ describe('local console client tests', () => { }); it('deletes all objects in the directory if config file has a parent directory', async () => { const mockConfigPath = `s3://${mockConfigBucket}/${mockUser}/${mockConfigFileName}`; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); process.env.CONFIG_PATH = mockConfigPath; mockListObjectsV2.mockResolvedValueOnce({ Contents: [ @@ -385,7 +385,7 @@ describe('local console client tests', () => { }); it('deletes single object if config file does not have a parent directory', async () => { const mockConfigPath = `s3://${mockConfigBucket}/${mockUser}/${mockConfigFileName}`; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); process.env.CONFIG_PATH = mockConfigPath; mockListObjectsV2.mockResolvedValueOnce({ Contents: [ @@ -432,7 +432,7 @@ describe('local console client tests', () => { }); it('logs and re-throws errors', async () => { const mockConfigPath = `s3://${mockConfigBucket}/${mockUser}/${mockConfigFileName}`; - const mockConsole = await ConsoleParser.fromJson(mockConsoleJson); + const mockConsole = await Console.fromJson(mockConsoleJson); const mockError = new Error(); process.env.CONFIG_PATH = mockConfigPath; mockListObjectsV2.mockImplementationOnce(() => { throw mockError; }); diff --git a/test/clients/page-client.test.ts b/test/clients/page-client.test.ts index 777033e..9e0bad3 100644 --- a/test/clients/page-client.test.ts +++ b/test/clients/page-client.test.ts @@ -9,19 +9,18 @@ jest.mock('../../src/clients/console-client/index.js', () => jest.fn().mockImple import DashboardClient from '../../src/clients/dashboard-client'; import HttpError from 'http-errors'; -import { ConsoleParser, DashboardParser } from '@tinystacks/ops-core'; -import { Dashboard } from '@tinystacks/ops-model'; -import { Console } from '@tinystacks/ops-model'; +import { Console, Dashboard } from '@tinystacks/ops-core'; +import { Console as ConsoleType, Dashboard as DashboardType } from '@tinystacks/ops-model'; const mockSpyGetDashboard = jest.spyOn(DashboardClient, 'getDashboard'); -const basicDashboardJson: Dashboard = { +const basicDashboardJson: DashboardType = { id: 'MockRoute', route: '/mock-route', widgetIds: [] }; -const basicConsoleJson: Console = { +const basicConsoleJson: ConsoleType = { name: 'mock-console', dashboards: {}, providers: {}, @@ -84,8 +83,8 @@ describe('dashboard client tests', () => { }); describe('getDashboard', () => { it('returns dashboard from console matching the route specified', async () => { - const mockDashboard = DashboardParser.fromJson(basicDashboardJson); - const mockConsole = ConsoleParser.fromJson({ + const mockDashboard = Dashboard.fromJson(basicDashboardJson); + const mockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { MockRoute: mockDashboard } }); @@ -96,7 +95,7 @@ describe('dashboard client tests', () => { expect(result).toEqual(mockDashboard); }); it('throws not found if dashboard does not exist on the console', async () => { - const mockConsole = ConsoleParser.fromJson(basicConsoleJson); + const mockConsole = Console.fromJson(basicConsoleJson); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; @@ -107,7 +106,7 @@ describe('dashboard client tests', () => { } finally { expect(thrownError).toBeDefined(); expect(thrownError).toEqual( - HttpError.NotFound('Dashboard with id MockRoute does not exist in console mock-console!') + HttpError.NotFound('DashboardType with id MockRoute does not exist in console mock-console!') ); } }); @@ -115,8 +114,8 @@ describe('dashboard client tests', () => { describe('getDashboards', () => { it('returns dashboards from console', async () => { - const mockDashboard = DashboardParser.fromJson(basicDashboardJson); - const mockConsole = await ConsoleParser.fromJson({ + const mockDashboard = Dashboard.fromJson(basicDashboardJson); + const mockConsole = await Console.fromJson({ ...basicConsoleJson, dashboards: { MockRoute: mockDashboard } }); @@ -142,13 +141,13 @@ describe('dashboard client tests', () => { }); describe('createDashboard', () => { it('saves dashboard to console and returns saved dashboard', async () => { - const mockDashboard = DashboardParser.fromJson({ + const mockDashboard = Dashboard.fromJson({ id: 'mock-dashboard', route: '/mock-route', widgetIds: [] - } as Dashboard); - const mockConsole = await ConsoleParser.fromJson(basicConsoleJson); - const mockSavedConsole = await ConsoleParser.fromJson({ + } as DashboardType); + const mockConsole = await Console.fromJson(basicConsoleJson); + const mockSavedConsole = await Console.fromJson({ ...basicConsoleJson, dashboards: { 'mock-dashboard': { @@ -175,17 +174,17 @@ describe('dashboard client tests', () => { mockSaveConsole.mockReset(); }); it('throws Conflict if dashboard with id already exists on console', async () => { - const mockDashboard = DashboardParser.fromJson({ + const mockDashboard = Dashboard.fromJson({ id: 'mock-dashboard', route: '/mock-route', widgetIds: [] - } as Dashboard); - const mockConsole = ConsoleParser.fromJson({ + } as DashboardType); + const mockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { 'mock-dashboard': mockDashboard } - } as Console); + } as ConsoleType); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; @@ -205,12 +204,12 @@ describe('dashboard client tests', () => { } }); it('throws Conflict if dashboard with route already exists on console', async () => { - const mockDashboard = DashboardParser.fromJson({ + const mockDashboard = Dashboard.fromJson({ id: 'mock-dashboard', route: '/mock-route', widgetIds: [] }); - const mockConsole = ConsoleParser.fromJson({ + const mockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { MainDashboard: mockDashboard @@ -238,23 +237,23 @@ describe('dashboard client tests', () => { describe('updateDashboard', () => { it('saves dashboard to console and returns saved dashboard', async () => { - const oldMockDashboard = DashboardParser.fromJson({ + const oldMockDashboard = Dashboard.fromJson({ id: 'MockRoute', route: '/mock-route', widgetIds: [] }); - const oldMockConsole = ConsoleParser.fromJson({ + const oldMockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { MockRoute: oldMockDashboard } }); - const newMockDashboard = DashboardParser.fromJson({ + const newMockDashboard = Dashboard.fromJson({ id: 'MockRoute', route: '/mock-route', widgetIds: [] }); - const newMockConsole = ConsoleParser.fromJson({ + const newMockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { MockRoute: newMockDashboard @@ -272,12 +271,12 @@ describe('dashboard client tests', () => { expect(result).toEqual(newMockDashboard); }); it('throws NotFound if dashboard does not exist on console', async () => { - const mockDashboard = DashboardParser.fromJson({ + const mockDashboard = Dashboard.fromJson({ id: 'MockRoute', route: '/mock-route', widgetIds: [] }); - const mockConsole = ConsoleParser.fromJson({ + const mockConsole = Console.fromJson({ ...basicConsoleJson }); mockGetConsole.mockResolvedValueOnce(mockConsole); @@ -303,12 +302,12 @@ describe('dashboard client tests', () => { describe('deleteDashboard', () => { it('deletes dashboard from console and returns deleted dashboard', async () => { - const mockDashboard = DashboardParser.fromJson({ + const mockDashboard = Dashboard.fromJson({ id: 'MockRoute', route: '/mock-route', widgetIds: [] }); - const mockConsole = ConsoleParser.fromJson({ + const mockConsole = Console.fromJson({ ...basicConsoleJson, dashboards: { MockRoute: mockDashboard @@ -323,7 +322,7 @@ describe('dashboard client tests', () => { expect(result).toEqual(mockDashboard); }); it('throws NotFound if dashboard does not exist on console', async () => { - const mockConsole = ConsoleParser.fromJson(basicConsoleJson); + const mockConsole = Console.fromJson(basicConsoleJson); mockGetConsole.mockResolvedValueOnce(mockConsole); jest.spyOn(DashboardClient, 'getDashboard'); diff --git a/test/clients/widget-client.test.ts b/test/clients/widget-client.test.ts index ed98f35..d23fd3d 100644 --- a/test/clients/widget-client.test.ts +++ b/test/clients/widget-client.test.ts @@ -7,8 +7,8 @@ jest.mock('../../src/clients/console-client/index.js', () => mockConsoleClient); import WidgetClient from '../../src/clients/widget-client.js'; import HttpError from 'http-errors'; import { BasicWidget } from '../utils/basic-widget.js'; -import { ConsoleParser } from '@tinystacks/ops-core'; -import { Widget, Console, Parameter } from '@tinystacks/ops-model'; +import { Console } from '@tinystacks/ops-core'; +import { Widget, Console as ConsoleType, Parameter } from '@tinystacks/ops-model'; const basicConsole = { name: 'mock-console', @@ -23,7 +23,7 @@ const basicWidget: Widget = { displayName: 'Mock Widget', type: 'BasicWidget' }; -const basicConsoleWithWidget: Console = { +const basicConsoleWithWidget: ConsoleType = { ...basicConsole, widgets: { [basicWidget.id]: basicWidget @@ -96,7 +96,7 @@ describe('widget client tests', () => { describe('getWidget', () => { it('returns widget from console matching the id specified', async () => { const mockWidget = basicWidget; - const mockConsole = await ConsoleParser.fromJson(basicConsoleWithWidget); + const mockConsole = await Console.fromJson(basicConsoleWithWidget); mockGetConsole.mockResolvedValueOnce(mockConsole); const result = await WidgetClient.getWidget({ consoleName: basicConsoleWithWidget.name, @@ -106,7 +106,7 @@ describe('widget client tests', () => { expect(result).toEqual(mockWidget); }); it('throws not found if widget does not exist on the console', async () => { - const mockConsole = ConsoleParser.fromJson(basicConsole); + const mockConsole = Console.fromJson(basicConsole); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; @@ -249,7 +249,7 @@ describe('widget client tests', () => { describe('getWidgets', () => { it('returns widgets from console', async () => { const mockWidget = BasicWidget.fromJson(basicWidget); - const mockConsole = await ConsoleParser.fromJson(basicConsoleWithWidget); + const mockConsole = await Console.fromJson(basicConsoleWithWidget); mockGetConsole.mockResolvedValueOnce(mockConsole); const result = await WidgetClient.getWidget({ @@ -280,11 +280,11 @@ describe('widget client tests', () => { describe('createWidget', () => { it('saves widget to console and returns saved widget', async () => { const mockWidget = BasicWidget.fromJson(basicWidget); - const mockConsole = await ConsoleParser.fromJson({ + const mockConsole = await Console.fromJson({ ...basicConsole, dependencies: { ...basicConsoleWithWidget.dependencies } }); - const mockSavedConsole = await ConsoleParser.fromJson(basicConsoleWithWidget); + const mockSavedConsole = await Console.fromJson(basicConsoleWithWidget); mockGetConsole.mockResolvedValueOnce(mockConsole); mockGetConsole.mockResolvedValueOnce(mockSavedConsole); const result = await WidgetClient.createWidget('mock-console', mockWidget); @@ -301,7 +301,7 @@ describe('widget client tests', () => { it('throws Conflict if widget already exists on console', async () => { const mockWidget = BasicWidget.fromJson(basicWidget); - const mockConsole = ConsoleParser.fromJson(basicConsoleWithWidget); + const mockConsole = Console.fromJson(basicConsoleWithWidget); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; try { @@ -323,7 +323,7 @@ describe('widget client tests', () => { describe('updateWidget', () => { it('saves widget to console and returns saved widget', async () => { const oldMockWidget = BasicWidget.fromJson(basicWidget); - const oldMockConsole = await ConsoleParser.fromJson({ + const oldMockConsole = await Console.fromJson({ ...basicConsoleWithWidget, widgets: { [oldMockWidget.id]: oldMockWidget @@ -333,7 +333,7 @@ describe('widget client tests', () => { ...basicWidget, displayName: 'Mock Widget 2' }); - const newMockConsole = await ConsoleParser.fromJson({ + const newMockConsole = await Console.fromJson({ ...basicConsoleWithWidget, widgets: { [newMockWidget.id]: newMockWidget @@ -350,7 +350,7 @@ describe('widget client tests', () => { }); it('throws NotFound if widget does not exist on console', async () => { const mockWidget = BasicWidget.fromJson(basicWidget); - const mockConsole = await ConsoleParser.fromJson(basicConsole); + const mockConsole = await Console.fromJson(basicConsole); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; try { @@ -372,7 +372,7 @@ describe('widget client tests', () => { describe('deleteWidget', () => { it('deletes widget from console and returns deleted widget', async () => { const mockWidget = BasicWidget.fromJson(basicWidget); - const mockConsole = await ConsoleParser.fromJson(basicConsoleWithWidget); + const mockConsole = await Console.fromJson(basicConsoleWithWidget); mockGetConsole.mockResolvedValueOnce(mockConsole); const result = await WidgetClient.deleteWidget('mock-console', basicWidget.id); @@ -382,7 +382,7 @@ describe('widget client tests', () => { expect(result).toEqual(mockWidget); }); it('throws NotFound if widget does not exist on console', async () => { - const mockConsole = await ConsoleParser.fromJson(basicConsole); + const mockConsole = await Console.fromJson(basicConsole); mockGetConsole.mockResolvedValueOnce(mockConsole); let thrownError; diff --git a/test/middleware/error.test.ts b/test/middleware/error.test.ts index faea9a4..26383ed 100644 --- a/test/middleware/error.test.ts +++ b/test/middleware/error.test.ts @@ -40,7 +40,7 @@ describe('error middleware tests', () => { expect(mockStatus).toBeCalled(); expect(mockStatus).toBeCalledWith(418); expect(mockJson).toBeCalled(); - expect(mockJson).toBeCalledWith({ status: 418, message: 'mock-error' }); + expect(mockJson).toBeCalledWith(TinyStacksError.fromJson(mockError)); expect(mockNext).toBeCalled(); }); @@ -49,7 +49,7 @@ describe('error middleware tests', () => { name: 'TinyStacksError', status: 418, message: 'mock-error', - type: TinyStacksErrorType.type.VALIDATION + type: 'Validation' }); await errorMiddleware(mockError, mockRequest, mockResponse, mockNext); @@ -59,7 +59,7 @@ describe('error middleware tests', () => { expect(mockStatus).toBeCalled(); expect(mockStatus).toBeCalledWith(418); expect(mockJson).toBeCalled(); - expect(mockJson).toBeCalledWith({ status: 418, message: 'mock-error' }); + expect(mockJson).toBeCalledWith(mockError); expect(mockNext).toBeCalled(); }); @@ -78,7 +78,7 @@ describe('error middleware tests', () => { expect(mockStatus).toBeCalled(); expect(mockStatus).toBeCalledWith(418); expect(mockJson).toBeCalled(); - expect(mockJson).toBeCalledWith({ status: 418, message: 'mock-error' }); + expect(mockJson).toBeCalledWith(mockError); expect(mockNext).toBeCalled(); }); diff --git a/test/utils/parsing-utils.test.ts b/test/utils/parsing-utils.test.ts index fa2ee31..edaee9a 100644 --- a/test/utils/parsing-utils.test.ts +++ b/test/utils/parsing-utils.test.ts @@ -1,4 +1,4 @@ -import { DashboardParser } from '@tinystacks/ops-core'; +import { Dashboard } from '@tinystacks/ops-core'; import { castParametersToDeclaredTypes, castToType, @@ -137,7 +137,7 @@ describe('parsing-utils', () => { } ] } - } as unknown as Record; + } as unknown as Record; it('returns empty object if there are no parameters', () => { const result = castParametersToDeclaredTypes('MockWidget'); From fd5abe3b5300be444a6a7a4b6ae34e3175dea103 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Thu, 15 Jun 2023 12:08:39 -0500 Subject: [PATCH 4/4] optional chaining --- src/clients/console-client/local.ts | 4 ++-- src/clients/console-client/s3.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/clients/console-client/local.ts b/src/clients/console-client/local.ts index 7eed787..fb8aade 100644 --- a/src/clients/console-client/local.ts +++ b/src/clients/console-client/local.ts @@ -22,8 +22,8 @@ class LocalConsoleClient implements IConsoleClient { if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); - if (!isNil(configJson.Console)) { - const consoleType: ConsoleType = ConsoleParser.parse(configJson.Console as YamlConsole); + if (!isNil(configJson?.Console)) { + const consoleType: ConsoleType = ConsoleParser.parse(configJson?.Console); return ConsoleParser.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!'); diff --git a/src/clients/console-client/s3.ts b/src/clients/console-client/s3.ts index 3d34b97..8ee410a 100644 --- a/src/clients/console-client/s3.ts +++ b/src/clients/console-client/s3.ts @@ -1,6 +1,6 @@ import isNil from 'lodash.isnil'; import { ConsoleParser } from '@tinystacks/ops-core'; -import { Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; +import { Config, Console as ConsoleType, YamlConsole } from '@tinystacks/ops-model'; import HttpError from 'http-errors'; import { mkdirSync, @@ -177,10 +177,10 @@ class S3ConsoleClient implements IConsoleClient { */ const configFile = await this.getConfig(); if (!configFile) throw HttpError.NotFound(`Cannot fetch console! Config file ${configPath} not found!`); - const configJson = Yaml.parseAs(configFile.toString()); + const configJson = Yaml.parseAs(configFile.toString()); // console.debug('configJson: ', JSON.stringify(configJson)); - if (!isNil(configJson)) { - const consoleType: ConsoleType = ConsoleParser.parse(configJson); + if (!isNil(configJson?.Console)) { + const consoleType: ConsoleType = ConsoleParser.parse(configJson?.Console); return ConsoleParser.fromJson(consoleType); } throw HttpError.InternalServerError('Cannot fetch console! The contents of the config file was empty or invalid!');