From 1deb025fab5a6d46f43a0b39899cf886398691de Mon Sep 17 00:00:00 2001 From: Arwed Mett Date: Tue, 28 May 2024 00:14:36 +0200 Subject: [PATCH 1/6] custom logger --- biome.json | 4 +--- src/index.ts | 1 + src/lib/config.ts | 7 +++++-- src/lib/logger.ts | 3 +++ src/types.ts | 2 ++ 5 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/lib/logger.ts diff --git a/biome.json b/biome.json index 18a0be7..261ad8b 100644 --- a/biome.json +++ b/biome.json @@ -20,9 +20,7 @@ }, "recommended": true, "correctness": { - "noUnusedVariables": "error" - }, - "nursery": { + "noUnusedVariables": "error", "noUnusedImports": "error" } } diff --git a/src/index.ts b/src/index.ts index bcfc33f..fe971f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export type { Adapter, Config } from "./types"; export { loadConfig } from "./lib/config"; +export { Logger } from "./lib/logger"; diff --git a/src/lib/config.ts b/src/lib/config.ts index fc89428..83fc62a 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,6 +1,7 @@ import { AnyZodObject, z } from "zod"; import { Adapter, Config } from "../types"; import { deepMerge } from "./adapters/utils"; +import { Logger } from "./logger"; /** * Load config from adapters. @@ -15,10 +16,12 @@ export const loadConfig = async ( config: Config, ): Promise> => { const { schema, adapters, onError, onSuccess } = config; + const logger = config.logger ?? console; // Read data from adapters const data = await getDataFromAdapters( Array.isArray(adapters) ? adapters : adapters ? [adapters] : [], + logger, ); // Validate data against schema @@ -43,7 +46,7 @@ export const loadConfig = async ( return result.data; }; -const getDataFromAdapters = async (adapters?: Adapter[]) => { +const getDataFromAdapters = async (adapters: Adapter[], logger: Logger) => { // If no adapters are provided, we will read from process.env if (!adapters || adapters.length === 0) { return process.env; @@ -55,7 +58,7 @@ const getDataFromAdapters = async (adapters?: Adapter[]) => { try { return await adapter.read(); } catch (error) { - console.warn( + logger.warn( `Cannot read data from ${adapter.name}: ${ error instanceof Error ? error.message : error }`, diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 0000000..a132dff --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,3 @@ +export type Logger = { + warn: (message: string) => void; +}; diff --git a/src/types.ts b/src/types.ts index 45b3a01..e679274 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import {Logger} from "./lib/logger"; export type Adapter = { name: string; @@ -10,4 +11,5 @@ export type Config = { adapters?: Adapter[] | Adapter; onSuccess?: (data: z.infer) => void; onError?: (error: z.ZodError>) => void; + logger: Logger }; From 0fcdeaf8e27cab83992d3e073cd338c278c1dfc4 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 28 May 2024 19:36:17 +0100 Subject: [PATCH 2/6] chore: small refactor + add unit tests for custom logger --- biome.json | 4 +++- src/lib/config.ts | 3 +-- src/lib/logger.ts | 3 --- src/types.ts | 7 +++++-- tests/config.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 50 insertions(+), 10 deletions(-) delete mode 100644 src/lib/logger.ts diff --git a/biome.json b/biome.json index 261ad8b..18a0be7 100644 --- a/biome.json +++ b/biome.json @@ -20,7 +20,9 @@ }, "recommended": true, "correctness": { - "noUnusedVariables": "error", + "noUnusedVariables": "error" + }, + "nursery": { "noUnusedImports": "error" } } diff --git a/src/lib/config.ts b/src/lib/config.ts index 83fc62a..81d2daf 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,7 +1,6 @@ import { AnyZodObject, z } from "zod"; -import { Adapter, Config } from "../types"; +import { Adapter, Config, Logger } from "../types"; import { deepMerge } from "./adapters/utils"; -import { Logger } from "./logger"; /** * Load config from adapters. diff --git a/src/lib/logger.ts b/src/lib/logger.ts deleted file mode 100644 index a132dff..0000000 --- a/src/lib/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type Logger = { - warn: (message: string) => void; -}; diff --git a/src/types.ts b/src/types.ts index e679274..a32d205 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import {Logger} from "./lib/logger"; export type Adapter = { name: string; @@ -11,5 +10,9 @@ export type Config = { adapters?: Adapter[] | Adapter; onSuccess?: (data: z.infer) => void; onError?: (error: z.ZodError>) => void; - logger: Logger + logger?: Logger; +}; + +export type Logger = { + warn: (message: string) => void; }; diff --git a/tests/config.test.ts b/tests/config.test.ts index 6a1c096..0e0329a 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -6,7 +6,7 @@ import { dotEnvAdapter } from "../src/lib/adapters/dotenv-adapter"; import { envAdapter } from "../src/lib/adapters/env-adapter"; import { jsonAdapter } from "../src/lib/adapters/json-adapter"; import { loadConfig } from "../src/lib/config"; -import { Adapter } from "../src/types"; +import { Adapter, Logger } from "../src/types"; describe("Load config tests", () => { describe("default adapter", () => { @@ -280,7 +280,7 @@ describe("Load config tests", () => { expect(config.PORT).toBe("3000"); expect(config.APP_NAME).toBe("app name"); }); - it("should return parsed data from adapters when schema is valid even if one adapter fails", async () => { + it("should return parsed data from adapters when schema is valid even if one adapter fails (default logger)", async () => { // given const schema = z.object({ HOST: z.string(), @@ -312,6 +312,45 @@ describe("Load config tests", () => { expect(config.PORT).toBe("3000"); expect(config.APP_NAME).toBe("app name"); }); + it("should return parsed data from adapters when schema is valid even if one adapter fails (custom logger)", async () => { + // given + const schema = z.object({ + HOST: z.string(), + PORT: z.string().regex(/^\d+$/), + APP_NAME: z.string(), + }); + process.env = { + HOST: "localhost", + PORT: "3000", + APP_NAME: "app name", + }; + + const customLogger: Logger = { + warn: () => {}, + }; + + const customLoggerWarnSpy = vi.spyOn(customLogger, "warn"); + const consoleErrorSpy = vi.spyOn(console, "warn"); + + // when + const config = await loadConfig({ + schema, + adapters: [ + envAdapter(), + jsonAdapter({ + path: "not-exist.json", + }), + ], + logger: customLogger, + }); + + // then + expect(customLoggerWarnSpy).toHaveBeenCalledOnce(); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + expect(config.HOST).toBe("localhost"); + expect(config.PORT).toBe("3000"); + expect(config.APP_NAME).toBe("app name"); + }); it("should return parsed data from adapters when schema is valid overriding previous adapters", async () => { // given const schema = z.object({ From 59f1ff3678661d38d1b5b9465308b726b82ec9ff Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 28 May 2024 19:42:09 +0100 Subject: [PATCH 3/6] chore: fix biome config --- biome.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/biome.json b/biome.json index 18a0be7..261ad8b 100644 --- a/biome.json +++ b/biome.json @@ -20,9 +20,7 @@ }, "recommended": true, "correctness": { - "noUnusedVariables": "error" - }, - "nursery": { + "noUnusedVariables": "error", "noUnusedImports": "error" } } From 18a72a4100364a5c52bf3be6da47916d58ec8f8f Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 28 May 2024 19:50:35 +0100 Subject: [PATCH 4/6] chore: upgrade biome and use dev dep version --- package.json | 33 +++++-------------- pnpm-lock.yaml | 82 +++++++++++++++++++++++++++++------------------ src/lib/config.ts | 4 +-- 3 files changed, 61 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index da4d05f..b440fd3 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "build": "tsup", "prepublishOnly": "pnpm build", "type-check": "tsc --noEmit", - "format": "pnpm dlx @biomejs/biome format ./src/**/*.ts --write", - "lint": "pnpm dlx @biomejs/biome lint ./src/**/*.ts", + "format": "biome format ./src/**/*.ts --write", + "lint": "biome lint ./src/**/*.ts", "test": "vitest run --silent", "test:watch": "vitest", "prebuild": "pnpm run type-check", @@ -21,9 +21,7 @@ "release:dry-run": "standard-version --skip.changelog --dry-run", "yalc:publish": "yalc publish" }, - "files": [ - "dist/**" - ], + "files": ["dist/**"], "exports": { ".": { "types": "./dist/index.d.ts", @@ -52,15 +50,9 @@ }, "typesVersions": { "*": { - "env-adapter": [ - "./dist/env-adapter.d.ts" - ], - "json-adapter": [ - "./dist/json-adapter.d.ts" - ], - "dotenv-adapter": [ - "./dist/dotenv-adapter.d.ts" - ] + "env-adapter": ["./dist/env-adapter.d.ts"], + "json-adapter": ["./dist/json-adapter.d.ts"], + "dotenv-adapter": ["./dist/dotenv-adapter.d.ts"] } }, "publishConfig": { @@ -74,18 +66,9 @@ "url": "https://github.com/alexmarqs/zod-config/issues" }, "homepage": "https://github.com/alexmarqs/zod-config#readme", - "keywords": [ - "zod", - "config", - "env", - "json", - "dotenv", - "typescript", - "adapters", - "typesafe" - ], + "keywords": ["zod", "config", "env", "json", "dotenv", "typescript", "adapters", "typesafe"], "devDependencies": { - "@biomejs/biome": "1.4.1", + "@biomejs/biome": "1.7.3", "@types/node": "20.10.7", "dotenv": "16.4.1", "standard-version": "9.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b168e3e..2f9834b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: devDependencies: '@biomejs/biome': - specifier: 1.4.1 - version: 1.4.1 + specifier: 1.7.3 + version: 1.7.3 '@types/node': specifier: 20.10.7 version: 20.10.7 @@ -54,68 +54,88 @@ packages: js-tokens: 4.0.0 dev: true - /@biomejs/biome@1.4.1: - resolution: {integrity: sha512-JccVAwPbhi37pdxbAGmaOBjUTKEwEjWAhl7rKkVVuXHo4MLASXJ5HR8BTgrImi4/7rTBsGz1tgVD1Kwv1CHGRg==} - engines: {node: '>=14.*'} + /@biomejs/biome@1.7.3: + resolution: {integrity: sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ==} + engines: {node: '>=14.21.3'} hasBin: true requiresBuild: true optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.4.1 - '@biomejs/cli-darwin-x64': 1.4.1 - '@biomejs/cli-linux-arm64': 1.4.1 - '@biomejs/cli-linux-x64': 1.4.1 - '@biomejs/cli-win32-arm64': 1.4.1 - '@biomejs/cli-win32-x64': 1.4.1 - dev: true - - /@biomejs/cli-darwin-arm64@1.4.1: - resolution: {integrity: sha512-PZWy2Idndqux38p6AXSDQM2ldRAWi32bvb7bMbTN0ALzpWYMYnxd71ornatumSSJYoNhKmxzDLq+jct7nZJ79w==} - engines: {node: '>=14.*'} + '@biomejs/cli-darwin-arm64': 1.7.3 + '@biomejs/cli-darwin-x64': 1.7.3 + '@biomejs/cli-linux-arm64': 1.7.3 + '@biomejs/cli-linux-arm64-musl': 1.7.3 + '@biomejs/cli-linux-x64': 1.7.3 + '@biomejs/cli-linux-x64-musl': 1.7.3 + '@biomejs/cli-win32-arm64': 1.7.3 + '@biomejs/cli-win32-x64': 1.7.3 + dev: true + + /@biomejs/cli-darwin-arm64@1.7.3: + resolution: {integrity: sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@biomejs/cli-darwin-x64@1.4.1: - resolution: {integrity: sha512-soj3BWhnsM1M2JlzR09cibUzG1owJqetwj/Oo7yg0foijo9lNH9XWXZfJBYDKgW/6Fomn+CC2EcUS+hisQzt9g==} - engines: {node: '>=14.*'} + /@biomejs/cli-darwin-x64@1.7.3: + resolution: {integrity: sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@biomejs/cli-linux-arm64@1.4.1: - resolution: {integrity: sha512-YIZqfJUg4F+fPsBTXxgD7EU2E5OAYbmYSl/snf4PevwfQCWE/omOFZv+NnIQmjYj9I7ParDgcJvanoA3/kO0JQ==} - engines: {node: '>=14.*'} + /@biomejs/cli-linux-arm64-musl@1.7.3: + resolution: {integrity: sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.7.3: + resolution: {integrity: sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@biomejs/cli-linux-x64@1.4.1: - resolution: {integrity: sha512-9YOZw3qBd/KUj63A6Hn2zZgzGb2nbESM0qNmeMXgmqinVKM//uc4OgY5TuKITuGjMSvcVxxd4dX1IzYjV9qvNQ==} - engines: {node: '>=14.*'} + /@biomejs/cli-linux-x64-musl@1.7.3: + resolution: {integrity: sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.7.3: + resolution: {integrity: sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@biomejs/cli-win32-arm64@1.4.1: - resolution: {integrity: sha512-nWQbvkNKxYn/kCQ0yVF8kCaS3VzaGvtFSmItXiMknU4521LDjJ7tNWH12Gol+pIslrCbd4E1LhJa0a3ThRsBVg==} - engines: {node: '>=14.*'} + /@biomejs/cli-win32-arm64@1.7.3: + resolution: {integrity: sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@biomejs/cli-win32-x64@1.4.1: - resolution: {integrity: sha512-88fR2CQxQ4YLs2BUDuywWYQpUKgU3A3sTezANFc/4LGKQFFLV2yX+F7QAdZVkMHfA+RD9Xg178HomM/6mnTNPA==} - engines: {node: '>=14.*'} + /@biomejs/cli-win32-x64@1.7.3: + resolution: {integrity: sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] requiresBuild: true diff --git a/src/lib/config.ts b/src/lib/config.ts index 81d2daf..53eab24 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,5 +1,5 @@ -import { AnyZodObject, z } from "zod"; -import { Adapter, Config, Logger } from "../types"; +import type { AnyZodObject, z } from "zod"; +import type { Adapter, Config, Logger } from "../types"; import { deepMerge } from "./adapters/utils"; /** From e07d0395975116b996622257a0cbcfb98839067a Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Tue, 28 May 2024 19:52:19 +0100 Subject: [PATCH 5/6] chore: fix exported type (due to move refactoring) --- src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index fe971f0..5caa9ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,2 @@ -export type { Adapter, Config } from "./types"; +export type { Adapter, Config, Logger } from "./types"; export { loadConfig } from "./lib/config"; -export { Logger } from "./lib/logger"; From 0f69f84df371c3760f1520470373d2708bbc9f04 Mon Sep 17 00:00:00 2001 From: Alexandre Marques Date: Wed, 29 May 2024 07:54:44 +0100 Subject: [PATCH 6/6] chore: add silentFail for adapters + unit tests --- package.json | 4 +- src/lib/adapters/dotenv-adapter/index.ts | 8 +- src/lib/adapters/env-adapter/index.ts | 6 +- src/lib/adapters/json-adapter/index.ts | 8 +- src/lib/config.ts | 12 +- src/types.ts | 3 +- tests/config.test.ts | 194 +++++++++++++++++------ 7 files changed, 173 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index b440fd3..ec4afa6 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "build": "tsup", "prepublishOnly": "pnpm build", "type-check": "tsc --noEmit", - "format": "biome format ./src/**/*.ts --write", - "lint": "biome lint ./src/**/*.ts", + "format": "biome format ./src --write", + "lint": "biome lint ./src", "test": "vitest run --silent", "test:watch": "vitest", "prebuild": "pnpm run type-check", diff --git a/src/lib/adapters/dotenv-adapter/index.ts b/src/lib/adapters/dotenv-adapter/index.ts index 22b1bca..259f247 100644 --- a/src/lib/adapters/dotenv-adapter/index.ts +++ b/src/lib/adapters/dotenv-adapter/index.ts @@ -1,16 +1,17 @@ import { parse } from "dotenv"; -import { readFile } from "fs/promises"; -import { Adapter } from "../../../types"; +import { readFile } from "node:fs/promises"; +import type { Adapter } from "../../../types"; import { filterByPrefixKey } from "../utils"; export type DotEnvAdapterProps = { path: string; prefixKey?: string; + silentFail?: boolean; }; const ADAPTER_NAME = "dotenv adapter"; -export const dotEnvAdapter = ({ path, prefixKey }: DotEnvAdapterProps): Adapter => { +export const dotEnvAdapter = ({ path, prefixKey, silentFail }: DotEnvAdapterProps): Adapter => { return { name: ADAPTER_NAME, read: async () => { @@ -32,5 +33,6 @@ export const dotEnvAdapter = ({ path, prefixKey }: DotEnvAdapterProps): Adapter ); } }, + silentFail, }; }; diff --git a/src/lib/adapters/env-adapter/index.ts b/src/lib/adapters/env-adapter/index.ts index bb0c66f..ade3573 100644 --- a/src/lib/adapters/env-adapter/index.ts +++ b/src/lib/adapters/env-adapter/index.ts @@ -1,14 +1,15 @@ -import { Adapter } from "../../../types"; +import type { Adapter } from "../../../types"; import { filterByPrefixKey } from "../utils"; export type EnvAdapterProps = { customEnv?: Record; prefixKey?: string; + silentFail?: boolean; }; const ADAPTER_NAME = "env adapter"; -export const envAdapter = ({ customEnv, prefixKey }: EnvAdapterProps = {}): Adapter => { +export const envAdapter = ({ customEnv, prefixKey, silentFail }: EnvAdapterProps = {}): Adapter => { return { name: ADAPTER_NAME, read: async () => { @@ -20,5 +21,6 @@ export const envAdapter = ({ customEnv, prefixKey }: EnvAdapterProps = {}): Adap return data; }, + silentFail, }; }; diff --git a/src/lib/adapters/json-adapter/index.ts b/src/lib/adapters/json-adapter/index.ts index 7bbf2a2..4adc2df 100644 --- a/src/lib/adapters/json-adapter/index.ts +++ b/src/lib/adapters/json-adapter/index.ts @@ -1,15 +1,16 @@ -import { Adapter } from "../../../types"; +import type { Adapter } from "../../../types"; import { filterByPrefixKey } from "../utils"; -import { readFile } from "fs/promises"; +import { readFile } from "node:fs/promises"; export type JsonAdapterProps = { path: string; prefixKey?: string; + silentFail?: boolean; }; const ADAPTER_NAME = "json adapter"; -export const jsonAdapter = ({ path, prefixKey }: JsonAdapterProps): Adapter => { +export const jsonAdapter = ({ path, prefixKey, silentFail }: JsonAdapterProps): Adapter => { return { name: ADAPTER_NAME, read: async () => { @@ -31,5 +32,6 @@ export const jsonAdapter = ({ path, prefixKey }: JsonAdapterProps): Adapter => { ); } }, + silentFail, }; }; diff --git a/src/lib/config.ts b/src/lib/config.ts index 53eab24..6ea7950 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -57,11 +57,13 @@ const getDataFromAdapters = async (adapters: Adapter[], logger: Logger) => { try { return await adapter.read(); } catch (error) { - logger.warn( - `Cannot read data from ${adapter.name}: ${ - error instanceof Error ? error.message : error - }`, - ); + if (!adapter.silentFail) { + logger.warn( + `Cannot read data from ${adapter.name}: ${ + error instanceof Error ? error.message : error + }`, + ); + } return {}; } }), diff --git a/src/types.ts b/src/types.ts index a32d205..17bea10 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,9 @@ -import { z } from "zod"; +import type { z } from "zod"; export type Adapter = { name: string; read: () => Promise>; + silentFail?: boolean; }; export type Config = { diff --git a/tests/config.test.ts b/tests/config.test.ts index 0e0329a..91ebe51 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -1,12 +1,12 @@ -import { unlink, writeFile } from "fs/promises"; -import path from "path"; +import { unlink, writeFile } from "node:fs/promises"; +import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { z } from "zod"; import { dotEnvAdapter } from "../src/lib/adapters/dotenv-adapter"; import { envAdapter } from "../src/lib/adapters/env-adapter"; import { jsonAdapter } from "../src/lib/adapters/json-adapter"; import { loadConfig } from "../src/lib/config"; -import { Adapter, Logger } from "../src/types"; +import type { Adapter, Logger } from "../src/types"; describe("Load config tests", () => { describe("default adapter", () => { @@ -44,11 +44,11 @@ describe("Load config tests", () => { // when // then - loadConfig({ - schema, - }).catch((err) => { - expectZodError(err); - }); + expect( + loadConfig({ + schema, + }), + ).rejects.toThrowError(z.ZodError); }); }); describe("json adapter", () => { @@ -90,14 +90,14 @@ describe("Load config tests", () => { // when // then - loadConfig({ - schema, - adapters: jsonAdapter({ - path: testFilePath, + expect( + loadConfig({ + schema, + adapters: jsonAdapter({ + path: testFilePath, + }), }), - }).catch((err) => { - expectZodError(err); - }); + ).rejects.toThrowError(z.ZodError); }); it("should log error from adapter errors + throw zod error when schema is invalid", async () => { // given @@ -109,17 +109,70 @@ describe("Load config tests", () => { // when // then - loadConfig({ - schema, - adapters: jsonAdapter({ - path: "not-exist.json", + await expect( + loadConfig({ + schema, + adapters: jsonAdapter({ + path: "not-exist.json", + }), }), - }).catch((err) => { - expectZodError(err); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Cannot read data from json adapter: Failed to parse / read JSON file at not-exist.json: ENOENT: no such file or directory, open 'not-exist.json'", - ); + ).rejects.toThrowError(z.ZodError); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Cannot read data from json adapter: Failed to parse / read JSON file at not-exist.json: ENOENT: no such file or directory, open 'not-exist.json'", + ); + }); + it("should log error from adapter errors (custom logger) + throw zod error when schema is invalid", async () => { + // given + const schema = z.object({ + HOST: z.string(), + PORT: z.number(), }); + + const customLogger: Logger = { + warn: (_msg) => {}, + }; + + const customLoggerWarnSpy = vi.spyOn(customLogger, "warn"); + + // when + // then + await expect( + loadConfig({ + schema, + adapters: jsonAdapter({ + path: "not-exist.json", + }), + logger: customLogger, + }), + ).rejects.toThrowError(z.ZodError); + + expect(customLoggerWarnSpy).toHaveBeenCalledWith( + "Cannot read data from json adapter: Failed to parse / read JSON file at not-exist.json: ENOENT: no such file or directory, open 'not-exist.json'", + ); + }); + it("throw zod error when schema is invalid but not log error from adapter errors when silentFail is true", async () => { + // given + const schema = z.object({ + HOST: z.string(), + PORT: z.number(), + }); + + const consoleErrorSpy = vi.spyOn(console, "warn"); + + // when + // then + expect( + loadConfig({ + schema, + adapters: jsonAdapter({ + path: "not-exist.json", + silentFail: true, + }), + }), + ).rejects.toThrowError(z.ZodError); + + expect(consoleErrorSpy).not.toHaveBeenCalled(); }); }); describe("dotenv adapter", () => { @@ -161,14 +214,14 @@ describe("Load config tests", () => { // when // then - loadConfig({ - schema, - adapters: dotEnvAdapter({ - path: testFilePath, + expect( + loadConfig({ + schema, + adapters: dotEnvAdapter({ + path: testFilePath, + }), }), - }).catch((err) => { - expectZodError(err); - }); + ).rejects.toThrowError(z.ZodError); }); it("should log error from adapter errors + throw zod error when schema is invalid", async () => { // given @@ -180,17 +233,70 @@ describe("Load config tests", () => { // when // then - loadConfig({ - schema, - adapters: dotEnvAdapter({ - path: ".env.not-exist", + await expect( + loadConfig({ + schema, + adapters: dotEnvAdapter({ + path: ".env.not-exist", + }), + }), + ).rejects.toThrowError(z.ZodError); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Cannot read data from dotenv adapter: Failed to parse / read .env file at .env.not-exist: ENOENT: no such file or directory, open '.env.not-exist'", + ); + }); + it("should log error from adapter errors (custom logger) + throw zod error when schema is invalid", async () => { + // given + const schema = z.object({ + HOST: z.string(), + PORT: z.number(), + }); + + const customLogger: Logger = { + warn: (_msg) => {}, + }; + + const customLoggerWarnSpy = vi.spyOn(customLogger, "warn"); + + // when + // then + await expect( + loadConfig({ + schema, + adapters: dotEnvAdapter({ + path: ".env.not-exist", + }), + logger: customLogger, }), - }).catch((err) => { - expectZodError(err); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Cannot read data from dotenv adapter: Failed to parse / read .env file at .env.not-exist: ENOENT: no such file or directory, open '.env.not-exist'", - ); + ).rejects.toThrowError(z.ZodError); + + expect(customLoggerWarnSpy).toHaveBeenCalledWith( + "Cannot read data from dotenv adapter: Failed to parse / read .env file at .env.not-exist: ENOENT: no such file or directory, open '.env.not-exist'", + ); + }); + it("throw zod error when schema is invalid but not log error from adapter errors when silentFail is true", async () => { + // given + const schema = z.object({ + HOST: z.string(), + PORT: z.number(), }); + + const consoleErrorSpy = vi.spyOn(console, "warn"); + + // when + // then + expect( + loadConfig({ + schema, + adapters: dotEnvAdapter({ + path: ".env.not-exist", + silentFail: true, + }), + }), + ).rejects.toThrowError(z.ZodError); + + expect(consoleErrorSpy).not.toHaveBeenCalled(); }); }); describe("env adapter", () => { @@ -280,7 +386,7 @@ describe("Load config tests", () => { expect(config.PORT).toBe("3000"); expect(config.APP_NAME).toBe("app name"); }); - it("should return parsed data from adapters when schema is valid even if one adapter fails (default logger)", async () => { + it("should return parsed data from adapters when schema is valid even if one adapter fails", async () => { // given const schema = z.object({ HOST: z.string(), @@ -326,7 +432,7 @@ describe("Load config tests", () => { }; const customLogger: Logger = { - warn: () => {}, + warn: (_msg) => {}, }; const customLoggerWarnSpy = vi.spyOn(customLogger, "warn"); @@ -499,7 +605,3 @@ describe("Load config tests", () => { }); }); }); - -const expectZodError = (err: unknown) => { - expect(err).toBeInstanceOf(z.ZodError); -};