From d495d7b22240b3fc249a614ce6fde5240c10bb3a Mon Sep 17 00:00:00 2001 From: "Joaquin \"Florius\" Azcarate" Date: Sun, 3 Apr 2022 23:38:48 +0200 Subject: [PATCH] server: replace process.env with our env thoughout (#70) Bonus: tests! --- server/src/app.ts | 4 +- server/src/lib/__tests__/config.ts | 62 ++++++++++++++++++++++++++++++ server/src/lib/config.ts | 42 ++++++++++++-------- 3 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 server/src/lib/__tests__/config.ts diff --git a/server/src/app.ts b/server/src/app.ts index 8259fc7..635b3d2 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -20,8 +20,8 @@ app.use("/admin", admin) app.use("/health", health) app.get("/welcome", secretKey, (req, res) => { - const introContent = process.env.WELCOME_CONTENT - const introTitle = process.env.WELCOME_TITLE + const introContent = config.welcome_content + const introTitle = config.welcome_title if (!introContent || !introTitle) { return res.status(500).json({ error: "Missing welcome content" }) } diff --git a/server/src/lib/__tests__/config.ts b/server/src/lib/__tests__/config.ts new file mode 100644 index 0000000..84612f3 --- /dev/null +++ b/server/src/lib/__tests__/config.ts @@ -0,0 +1,62 @@ +import { config } from "../config" + +describe("Config", () => { + it("should build a valid development config when no environment is set", () => { + const emptyEnv = {}; + const result = config(emptyEnv); + + expect(result).toHaveProperty("is_production", false) + expect(result).toHaveProperty("port") + expect(result).toHaveProperty("jwt_secret") + expect(result).toHaveProperty("drift_home") + expect(result).toHaveProperty("memory_db") + expect(result).toHaveProperty("enable_admin") + expect(result).toHaveProperty("secret_key") + expect(result).toHaveProperty("registration_password") + expect(result).toHaveProperty("welcome_content") + expect(result).toHaveProperty("welcome_title") + }) + + it("should fail when building a prod environment without SECRET_KEY", () => { + expect(() => config({ NODE_ENV: "production" })) + .toThrow(new Error("Missing environment variable: SECRET_KEY")) + }) + + it("should build a prod config with a SECRET_KEY", () => { + const result = config({ NODE_ENV: "production", SECRET_KEY: "secret" }) + + expect(result).toHaveProperty("is_production", true) + expect(result).toHaveProperty("secret_key", "secret") + }) + + describe("jwt_secret", () => { + it("should use default jwt_secret when environment is blank string", () => { + const result = config({ JWT_SECRET: "" }) + + expect(result).toHaveProperty("is_production", false) + expect(result).toHaveProperty("jwt_secret", "myjwtsecret") + }) + }) + + describe("booleans", () => { + it("should parse 'true' as true", () => { + const result = config({ MEMORY_DB: "true" }) + + expect(result).toHaveProperty("memory_db", true) + }) + it("should parse 'false' as false", () => { + const result = config({ MEMORY_DB: "false" }) + + expect(result).toHaveProperty("memory_db", false) + }) + it("should fail when it is not parseable", () => { + expect(() => config({ MEMORY_DB: "foo" })) + .toThrow(new Error("Invalid boolean value: foo")) + }) + it("should default to false when the string is empty", () => { + const result = config({ MEMORY_DB: "" }) + + expect(result).toHaveProperty("memory_db", false) + }) + }) +}) diff --git a/server/src/lib/config.ts b/server/src/lib/config.ts index 1671100..9776f54 100644 --- a/server/src/lib/config.ts +++ b/server/src/lib/config.ts @@ -6,11 +6,16 @@ type Config = { memory_db: boolean enable_admin: boolean secret_key: string - registration_password: string + registration_password: string, + welcome_content: string | undefined, + welcome_title: string | undefined, } -const config = (): Config => { - const stringToBoolean = (str: string | undefined): boolean => { +type EnvironmentValue = string | undefined; +type Environment = { [key: string]: EnvironmentValue } + +export const config = (env: Environment): Config => { + const stringToBoolean = (str: EnvironmentValue): boolean => { if (str === "true") { return true } else if (str === "false") { @@ -22,21 +27,21 @@ const config = (): Config => { } } - const throwIfUndefined = (str: string | undefined, name: string): string => { + const throwIfUndefined = (str: EnvironmentValue, name: string): string => { if (str === undefined) { throw new Error(`Missing environment variable: ${name}`) } return str } - const defaultIfUndefined = (str: string | undefined, defaultValue: string): string => { + const defaultIfUndefined = (str: EnvironmentValue, defaultValue: string): string => { if (str === undefined) { return defaultValue } return str } - const validNodeEnvs = (str: string | undefined) => { + const validNodeEnvs = (str: EnvironmentValue) => { const valid = ["development", "production", "test"] if (str && !valid.includes(str)) { throw new Error(`Invalid NODE_ENV set: ${str}`) @@ -47,26 +52,29 @@ const config = (): Config => { } } - const is_production = process.env.NODE_ENV === "production"; + const is_production = env.NODE_ENV === "production"; - const developmentDefault = (str: string | undefined, name: string, defaultValue: string): string => { + const developmentDefault = (str: EnvironmentValue, name: string, defaultValue: string): string => { if (is_production) return throwIfUndefined(str, name); return defaultIfUndefined(str, defaultValue); } - validNodeEnvs(process.env.NODE_ENV) + validNodeEnvs(env.NODE_ENV) const config: Config = { - port: process.env.PORT ? parseInt(process.env.PORT) : 3000, - jwt_secret: process.env.JWT_SECRET || "myjwtsecret", - drift_home: process.env.DRIFT_HOME || "~/.drift", + port: env.PORT ? parseInt(env.PORT) : 3000, + jwt_secret: env.JWT_SECRET || "myjwtsecret", + drift_home: env.DRIFT_HOME || "~/.drift", is_production, - memory_db: stringToBoolean(process.env.MEMORY_DB), - enable_admin: stringToBoolean(process.env.ENABLE_ADMIN), - secret_key: developmentDefault(process.env.SECRET_KEY, "SECRET_KEY", "secret"), - registration_password: process.env.REGISTRATION_PASSWORD || "" + memory_db: stringToBoolean(env.MEMORY_DB), + enable_admin: stringToBoolean(env.ENABLE_ADMIN), + secret_key: developmentDefault(env.SECRET_KEY, "SECRET_KEY", "secret"), + registration_password: env.REGISTRATION_PASSWORD ?? "", + welcome_content: env.WELCOME_CONTENT, + welcome_title: env.WELCOME_TITLE + } return config } -export default config() +export default config(process.env)