From b2a0cafe6e4b065cdcd0a5b74e934fffe4aac330 Mon Sep 17 00:00:00 2001 From: Silas Date: Thu, 12 Sep 2024 09:07:19 -0400 Subject: [PATCH] Improve test coverage --- .eslintrc | 3 +- src/config/envs/index.spec.ts | 33 ++++++++ src/config/logger/index.spec.ts | 68 ++++++++++++++++ src/controllers/pages/IndexController.spec.ts | 38 +++++++++ src/index.spec.ts | 79 +++++++++++++++++++ src/index.ts | 8 +- tsconfig.json | 3 +- vitest.config.mts | 5 +- 8 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 src/config/envs/index.spec.ts create mode 100644 src/config/logger/index.spec.ts create mode 100644 src/controllers/pages/IndexController.spec.ts create mode 100644 src/index.spec.ts diff --git a/.eslintrc b/.eslintrc index 75d8303..2c479ce 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,7 @@ "rules": { "@typescript-eslint/no-inferrable-types": 0, "@typescript-eslint/no-unused-vars": 2, - "@typescript-eslint/no-var-requires": 0 + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-explicit-any": "off" } } diff --git a/src/config/envs/index.spec.ts b/src/config/envs/index.spec.ts new file mode 100644 index 0000000..7e5150c --- /dev/null +++ b/src/config/envs/index.spec.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +describe("Environment Configuration", () => { + const originalEnv = process.env; + + beforeEach(() => { + vi.resetModules(); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should set NODE_ENV to development by default", async () => { + delete process.env.NODE_ENV; + const { envs, isProduction } = await import("./index"); + expect(envs.NODE_ENV).toBe("development"); + expect(isProduction).toBe(false); + }); + + it("should use existing NODE_ENV if set", async () => { + process.env.NODE_ENV = "production"; + const { envs, isProduction } = await import("./index"); + expect(envs.NODE_ENV).toBe("production"); + expect(isProduction).toBe(true); + }); + + it("should export config from dotenv-flow", async () => { + const { config } = await import("./index"); + expect(config).toBeDefined(); + }); +}); diff --git a/src/config/logger/index.spec.ts b/src/config/logger/index.spec.ts new file mode 100644 index 0000000..1cf7230 --- /dev/null +++ b/src/config/logger/index.spec.ts @@ -0,0 +1,68 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { $log } from "@tsed/common"; + +vi.mock("@tsed/common", () => ({ + $log: { + appenders: { + set: vi.fn() + } + } +})); + +describe("Logger Configuration", () => { + const originalEnv = process.env; + + beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + vi.restoreAllMocks(); + }); + + it("should not set appenders in non-production environment", async () => { + process.env.NODE_ENV = "development"; + await import("./index"); + expect($log.appenders.set).not.toHaveBeenCalled(); + }); + + it("should set appenders in production environment", async () => { + process.env.NODE_ENV = "production"; + await import("./index"); + + expect($log.appenders.set).toHaveBeenCalledTimes(2); + expect($log.appenders.set).toHaveBeenCalledWith("stdout", { + type: "stdout", + levels: ["info", "debug"], + layout: { + type: "json" + } + }); + expect($log.appenders.set).toHaveBeenCalledWith("stderr", { + levels: ["trace", "fatal", "error", "warn"], + type: "stderr", + layout: { + type: "json" + } + }); + }); + + it("should export correct DILoggerOptions in non-production", async () => { + process.env.NODE_ENV = "development"; + const { default: loggerOptions } = await import("./index"); + expect(loggerOptions).toEqual({ + disableRoutesSummary: false + }); + }); + + it("should export correct DILoggerOptions in production", async () => { + process.env.NODE_ENV = "production"; + const { default: loggerOptions } = await import("./index"); + expect(loggerOptions).toEqual({ + disableRoutesSummary: true + }); + }); +}); diff --git a/src/controllers/pages/IndexController.spec.ts b/src/controllers/pages/IndexController.spec.ts new file mode 100644 index 0000000..18fe269 --- /dev/null +++ b/src/controllers/pages/IndexController.spec.ts @@ -0,0 +1,38 @@ +import { IndexController } from "./IndexController"; +import { describe, it, expect, beforeEach } from "vitest"; + +describe("IndexController", () => { + let controller: IndexController; + + beforeEach(() => { + controller = new IndexController(); + // @ts-expect-error - swagger is private + controller.swagger = [ + { path: "/api-docs", specVersion: "3.0.1" }, + { path: "/api-docs-2", specVersion: "3.0.2" } + ]; + }); + + it("should return correct view data", () => { + const protocol = "https"; + const host = "example.com"; + + const result = controller.get(protocol, host); + + expect(result).toEqual({ + BASE_URL: "https://example.com", + docs: [ + { url: "https://example.com/api-docs", path: "/api-docs", specVersion: "3.0.1" }, + { url: "https://example.com/api-docs-2", path: "/api-docs-2", specVersion: "3.0.2" } + ] + }); + }); + + it("should use http when protocol is not provided", () => { + const host = "example.com"; + + const result = controller.get("", host); + + expect(result.BASE_URL).toBe("http://example.com"); + }); +}); diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 0000000..b337c1e --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,79 @@ +import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest"; +import { Server } from "./Server"; + +vi.mock("@tsed/common", () => ({ + $log: { + error: vi.fn() + }, + PlatformApplication: vi.fn() +})); + +vi.mock("@tsed/platform-express", () => ({ + PlatformExpress: { + bootstrap: vi.fn() + } +})); + +describe("bootstrap function", () => { + let bootstrap: () => Promise; + let mockPlatform: { + listen: Mock; + stop: Mock; + }; + + beforeEach(async () => { + vi.clearAllMocks(); + + mockPlatform = { + listen: vi.fn().mockResolvedValue(undefined), + stop: vi.fn().mockResolvedValue(undefined) + }; + + const { PlatformExpress } = await import("@tsed/platform-express"); + (PlatformExpress.bootstrap as Mock).mockResolvedValue(mockPlatform); + + const module = await import("./index"); + bootstrap = module.bootstrap; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should bootstrap the server successfully", async () => { + await bootstrap(); + const { PlatformExpress } = await import("@tsed/platform-express"); + expect(PlatformExpress.bootstrap).toHaveBeenCalledWith(Server); + expect(mockPlatform.listen).toHaveBeenCalled(); + }); + + it("should handle errors during bootstrap", async () => { + const error = new Error("Bootstrap error"); + const { PlatformExpress } = await import("@tsed/platform-express"); + (PlatformExpress.bootstrap as Mock).mockRejectedValueOnce(error); + + await bootstrap(); + + const { $log } = await import("@tsed/common"); + expect($log.error).toHaveBeenCalledWith({ + event: "SERVER_BOOTSTRAP_ERROR", + message: error.message, + stack: error.stack + }); + }); + + it("should set up SIGINT handler", async () => { + const processOnSpy = vi.spyOn(process, "on"); + await bootstrap(); + expect(processOnSpy).toHaveBeenCalledWith("SIGINT", expect.any(Function)); + + // Simulate SIGINT + const sigintHandler = processOnSpy.mock.calls.find((call) => call[0] === "SIGINT")?.[1]; + if (sigintHandler && typeof sigintHandler === "function") { + sigintHandler(); + expect(mockPlatform.stop).toHaveBeenCalled(); + } else { + throw new Error("SIGINT handler not found or not a function"); + } + }); +}); diff --git a/src/index.ts b/src/index.ts index e79b726..ec2b031 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,10 @@ import { $log } from "@tsed/common"; import { PlatformExpress } from "@tsed/platform-express"; import { Server } from "./Server"; -async function bootstrap() { +export async function bootstrap() { try { const platform = await PlatformExpress.bootstrap(Server); await platform.listen(); - process.on("SIGINT", () => { platform.stop(); }); @@ -15,4 +14,7 @@ async function bootstrap() { } } -bootstrap(); +// Only call bootstrap if this file is being run directly +if (require.main === module) { + bootstrap(); +} diff --git a/tsconfig.json b/tsconfig.json index 844dcd9..29caea4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,8 @@ "resolveJsonModule": true, "useDefineForClassFields": false, "lib": ["es7", "dom", "ESNext.AsyncIterable"], - "typeRoots": ["./node_modules/@types"] + "typeRoots": ["./node_modules/@types"], + "noEmitHelpers": false }, "include": ["src"], "linterOptions": { diff --git a/vitest.config.mts b/vitest.config.mts index 9541517..2007dd9 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -4,7 +4,10 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - root: "./" + root: "./", + coverage: { + exclude: ["./processes.config.js", "vitest.config.mts"] + } }, plugins: [ // This is required to build the test files with SWC