From 8ee744e3becb8c7cf6f81fd5bb3e4c80269b4e4b Mon Sep 17 00:00:00 2001 From: Hendrik Belitz Date: Wed, 9 Jul 2025 14:05:05 +0200 Subject: [PATCH] Moved unit tests. createTenant now reflects UI prototype. --- docs/entity-relationship-diagram.md | 1 + .../crypto/{ => __tests__}/hashing.test.ts | 4 +- src/lib/crypto/{ => __tests__}/utils.test.ts | 4 +- .../{ => __tests__}/api-endpoint.test.ts | 0 .../{ => __tests__}/index.server.test.ts | 21 +++++----- src/lib/logger/{ => __tests__}/index.test.ts | 10 ++--- .../{ => __tests__}/integration.test.ts | 14 +++---- src/lib/server/openapi.ts | 1 + .../admin-account-service.test.ts | 41 +++---------------- .../tenant-admin-service.test.ts | 17 ++++---- .../server/services/admin-account-service.ts | 4 +- .../server/services/tenant-admin-service.ts | 25 +++++++++-- 12 files changed, 67 insertions(+), 75 deletions(-) rename src/lib/crypto/{ => __tests__}/hashing.test.ts (98%) rename src/lib/crypto/{ => __tests__}/utils.test.ts (99%) rename src/lib/logger/{ => __tests__}/api-endpoint.test.ts (100%) rename src/lib/logger/{ => __tests__}/index.server.test.ts (95%) rename src/lib/logger/{ => __tests__}/index.test.ts (96%) rename src/lib/logger/{ => __tests__}/integration.test.ts (95%) rename src/lib/server/services/{ => __tests__}/admin-account-service.test.ts (89%) rename src/lib/server/services/{ => __tests__}/tenant-admin-service.test.ts (92%) diff --git a/docs/entity-relationship-diagram.md b/docs/entity-relationship-diagram.md index c4c629b..4286886 100644 --- a/docs/entity-relationship-diagram.md +++ b/docs/entity-relationship-diagram.md @@ -92,6 +92,7 @@ erDiagram CHANNEL { uuid id PK string name + string color string description "Optional" boolean public "A channel may be only bookable with Code or internally" boolean require_confirmation "Must appointments be explicitly confirmed" diff --git a/src/lib/crypto/hashing.test.ts b/src/lib/crypto/__tests__/hashing.test.ts similarity index 98% rename from src/lib/crypto/hashing.test.ts rename to src/lib/crypto/__tests__/hashing.test.ts index 76c85a2..6ada051 100644 --- a/src/lib/crypto/hashing.test.ts +++ b/src/lib/crypto/__tests__/hashing.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { OptimizedArgon2 } from "./hashing"; -import type { Argon2Options } from "./hashing"; +import { OptimizedArgon2 } from "../hashing"; +import type { Argon2Options } from "../hashing"; // Mock the logger vi.mock("$lib/logger", () => ({ diff --git a/src/lib/crypto/utils.test.ts b/src/lib/crypto/__tests__/utils.test.ts similarity index 99% rename from src/lib/crypto/utils.test.ts rename to src/lib/crypto/__tests__/utils.test.ts index 855ce03..1e1b4a1 100644 --- a/src/lib/crypto/utils.test.ts +++ b/src/lib/crypto/__tests__/utils.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; -import { BufferUtils, KyberCrypto, AESCrypto, ShamirSecretSharing } from "./utils"; -import type { CryptoBuffer, KyberKeyPair, ShamirShare } from "./utils"; +import { BufferUtils, KyberCrypto, AESCrypto, ShamirSecretSharing } from "../utils"; +import type { CryptoBuffer, KyberKeyPair, ShamirShare } from "../utils"; describe("BufferUtils", () => { describe("from()", () => { diff --git a/src/lib/logger/api-endpoint.test.ts b/src/lib/logger/__tests__/api-endpoint.test.ts similarity index 100% rename from src/lib/logger/api-endpoint.test.ts rename to src/lib/logger/__tests__/api-endpoint.test.ts diff --git a/src/lib/logger/index.server.test.ts b/src/lib/logger/__tests__/index.server.test.ts similarity index 95% rename from src/lib/logger/index.server.test.ts rename to src/lib/logger/__tests__/index.server.test.ts index 00944b4..d8cc2ff 100644 --- a/src/lib/logger/index.server.test.ts +++ b/src/lib/logger/__tests__/index.server.test.ts @@ -3,7 +3,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; // Mock $app/environment for server environment vi.mock("$app/environment", () => ({ - browser: false + browser: false, + dev: true })); // Mock winston logger @@ -15,7 +16,7 @@ const mockWinstonLogger = { }; // Mock winston module -vi.mock("./winston", () => ({ +vi.mock("../winston", () => ({ default: mockWinstonLogger })); @@ -28,7 +29,7 @@ describe("UniversalLogger - Server Side", () => { vi.clearAllMocks(); // Dynamic import after mocks are set up - const loggerModule = await import("./index"); + const loggerModule = await import("../index"); logger = loggerModule.logger; createLogger = loggerModule.createLogger; }); @@ -84,7 +85,7 @@ describe("UniversalLogger - Server Side", () => { it("should format messages without context prefix when no context set", async () => { // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.debug("Processing request", { requestId: "456" }); @@ -201,7 +202,7 @@ describe("UniversalLogger - Server Side", () => { it("should handle empty meta objects", async () => { // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.info("Message without meta"); @@ -222,7 +223,7 @@ describe("UniversalLogger - Server Side", () => { }; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.info("Test message", meta); @@ -244,7 +245,7 @@ describe("UniversalLogger - Server Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 150, 200); @@ -267,7 +268,7 @@ describe("UniversalLogger - Server Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 200, 404); @@ -290,7 +291,7 @@ describe("UniversalLogger - Server Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 5000, 500); @@ -313,7 +314,7 @@ describe("UniversalLogger - Server Side", () => { } as Request; // Create fresh loggers without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger1 = new UniversalLogger(); const freshLogger2 = new UniversalLogger(); diff --git a/src/lib/logger/index.test.ts b/src/lib/logger/__tests__/index.test.ts similarity index 96% rename from src/lib/logger/index.test.ts rename to src/lib/logger/__tests__/index.test.ts index 8fe862c..811f931 100644 --- a/src/lib/logger/index.test.ts +++ b/src/lib/logger/__tests__/index.test.ts @@ -50,7 +50,7 @@ describe("UniversalLogger - Client Side", () => { vi.spyOn(console, "error").mockImplementation(mockConsole.error); // Dynamic import after mocks are set up - const loggerModule = await import("./index"); + const loggerModule = await import("../index"); logger = loggerModule.logger; createLogger = loggerModule.createLogger; }); @@ -118,7 +118,7 @@ describe("UniversalLogger - Client Side", () => { it("should handle empty meta objects", async () => { // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.info("Message without meta"); @@ -192,7 +192,7 @@ describe("UniversalLogger - Client Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 150, 200); @@ -219,7 +219,7 @@ describe("UniversalLogger - Client Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 200, 404); @@ -250,7 +250,7 @@ describe("UniversalLogger - Client Side", () => { } as Request; // Create a fresh logger without context - const { UniversalLogger } = await import("./index"); + const { UniversalLogger } = await import("../index"); const freshLogger = new UniversalLogger(); freshLogger.logRequest(mockRequest, 1000, 500); diff --git a/src/lib/logger/integration.test.ts b/src/lib/logger/__tests__/integration.test.ts similarity index 95% rename from src/lib/logger/integration.test.ts rename to src/lib/logger/__tests__/integration.test.ts index cbb352d..5f82d06 100644 --- a/src/lib/logger/integration.test.ts +++ b/src/lib/logger/__tests__/integration.test.ts @@ -61,7 +61,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { }); // Import logger after mocks are set up - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("IntegrationTest"); // Trigger client error @@ -103,7 +103,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { // Mock server error response mockFetch.mockRejectedValueOnce(new Error("Network error")); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("ErrorHandling"); logger.error("Test error"); @@ -129,7 +129,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { statusText: "Internal Server Error" }); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("ServerError"); logger.error("Server unavailable"); @@ -189,7 +189,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { json: async () => ({ success: true }) }); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("NoForwarding"); // Log different levels @@ -214,7 +214,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { json: async () => ({ success: true }) }); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger1 = createLogger("Concurrent1"); const logger2 = createLogger("Concurrent2"); @@ -248,7 +248,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { json: async () => ({ success: true }) }); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("DataIntegrity"); const complexMeta = { @@ -285,7 +285,7 @@ describe("UniversalLogger Integration - Client Error Forwarding", () => { json: async () => ({ success: true }) }); - const { createLogger } = await import("./index"); + const { createLogger } = await import("../index"); const logger = createLogger("MessageIntegrity"); const originalMessage = "Original error message with special chars: áéíóú 中文 🚀"; diff --git a/src/lib/server/openapi.ts b/src/lib/server/openapi.ts index 49e3bfb..5963ae5 100644 --- a/src/lib/server/openapi.ts +++ b/src/lib/server/openapi.ts @@ -5,6 +5,7 @@ export type JsonSchema = { required?: string[]; format?: string; description?: string; + default?: unknown; example?: unknown; enum?: unknown[]; $ref?: string; diff --git a/src/lib/server/services/admin-account-service.test.ts b/src/lib/server/services/__tests__/admin-account-service.test.ts similarity index 89% rename from src/lib/server/services/admin-account-service.test.ts rename to src/lib/server/services/__tests__/admin-account-service.test.ts index 689a883..343f5a8 100644 --- a/src/lib/server/services/admin-account-service.test.ts +++ b/src/lib/server/services/__tests__/admin-account-service.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { NotFoundError } from "../utils/errors"; +import { NotFoundError } from "../../utils/errors"; // Mock the database module -vi.mock("../db", () => ({ +vi.mock("../../db", () => ({ centralDb: { insert: vi.fn(), select: vi.fn(), @@ -11,16 +11,6 @@ vi.mock("../db", () => ({ } })); -// Mock the logger -vi.mock("$lib/logger", () => ({ - default: { - setContext: vi.fn().mockReturnValue({ - debug: vi.fn(), - warn: vi.fn(), - error: vi.fn() - }) - } -})); // Mock uuid generation vi.mock("uuidv7", () => ({ @@ -32,27 +22,11 @@ vi.mock("date-fns", () => ({ addMinutes: vi.fn() })); -// Mock zod -vi.mock("zod/v4", () => ({ - default: { - object: vi.fn().mockReturnValue({ - safeParse: vi.fn().mockReturnValue({ success: true }) - }), - string: vi.fn().mockReturnValue({ - min: vi.fn().mockReturnThis(), - email: vi.fn().mockReturnThis() - }), - uuidv7: vi.fn().mockReturnValue({ - optional: vi.fn().mockReturnThis() - }), - date: vi.fn().mockReturnValue({ - optional: vi.fn().mockReturnThis() - }) - } -})); + +// Import the service after mocks are set up +import { AdminAccountService } from "../admin-account-service"; describe("AdminAccountService", () => { - let AdminAccountService: any; let mockCentralDb: any; let mockUuidv7: any; let mockAddMinutes: any; @@ -60,11 +34,8 @@ describe("AdminAccountService", () => { beforeEach(async () => { vi.clearAllMocks(); - // Import the service after mocks are set up - AdminAccountService = (await import("./admin-account-service")).AdminAccountService; - // Get mocked modules - const dbModule = await vi.importMock("../db"); + const dbModule = await vi.importMock("../../db"); mockCentralDb = dbModule.centralDb; const uuidModule = await vi.importMock("uuidv7"); diff --git a/src/lib/server/services/tenant-admin-service.test.ts b/src/lib/server/services/__tests__/tenant-admin-service.test.ts similarity index 92% rename from src/lib/server/services/tenant-admin-service.test.ts rename to src/lib/server/services/__tests__/tenant-admin-service.test.ts index 76db537..09f242f 100644 --- a/src/lib/server/services/tenant-admin-service.test.ts +++ b/src/lib/server/services/__tests__/tenant-admin-service.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; // Mock the database module -vi.mock("../db", () => ({ +vi.mock("../../db", () => ({ centralDb: { insert: vi.fn(), select: vi.fn(), @@ -12,7 +12,7 @@ vi.mock("../db", () => ({ })); // Mock TenantConfig -vi.mock("../db/tenant-config", () => ({ +vi.mock("../../db/tenant-config", () => ({ TenantConfig: { create: vi.fn() } @@ -25,8 +25,10 @@ vi.mock("$env/dynamic/private", () => ({ } })); +// Import the service after mocks are set up +import { TenantAdminService } from "../tenant-admin-service"; + describe("TenantAdminService", () => { - let TenantAdminService: any; let mockCentralDb: any; let mockGetTenantDb: any; let mockTenantConfig: any; @@ -34,15 +36,12 @@ describe("TenantAdminService", () => { beforeEach(async () => { vi.clearAllMocks(); - // Import the service after mocks are set up - TenantAdminService = (await import("./tenant-admin-service")).TenantAdminService; - // Get mocked modules - const dbModule = await vi.importMock("../db"); + const dbModule = await vi.importMock("../../db"); mockCentralDb = dbModule.centralDb; mockGetTenantDb = dbModule.getTenantDb; - const configModule = await vi.importMock("../db/tenant-config"); + const configModule = await vi.importMock("../../db/tenant-config"); mockTenantConfig = configModule.TenantConfig; }); @@ -54,7 +53,7 @@ describe("TenantAdminService", () => { it("should create a new tenant with default configuration", async () => { const newTenant = { shortName: "test-clinic", - longName: "Test Medical Clinic", + longName: "", description: "A test clinic" }; diff --git a/src/lib/server/services/admin-account-service.ts b/src/lib/server/services/admin-account-service.ts index 59d26e3..f068571 100644 --- a/src/lib/server/services/admin-account-service.ts +++ b/src/lib/server/services/admin-account-service.ts @@ -13,7 +13,7 @@ export type InsertAdminPasskey = InferInsertModel; + export class TenantAdminService { #config!: TenantConfig; #db: Awaited> | null = null; private constructor(public readonly tenantId: string) {} - static async createTenant(newTenant: centralSchema.InsertTenant) { + static async createTenant(request: TenantCreationRequest) { const log = logger.setContext("TenantAdminService"); + + const validation = tentantCreationSchema.safeParse(request); + + if (!validation.success) throw new ValidationError("Invalid tenant creation request"); + log.debug("Creating new tenant", { - shortName: newTenant.shortName, - longName: newTenant.longName + shortName: request.shortName }); const configuration: Record = { @@ -33,6 +47,8 @@ export class TenantAdminService { const urlParts = env.DATABASE_URL.split("/"); urlParts.pop(); + + const newTenant: InsertTenant = { ...request, longName: "", databaseUrl: "" }; newTenant.databaseUrl = urlParts.join("/") + "/" + newTenant.shortName; try { @@ -60,6 +76,9 @@ export class TenantAdminService { tenantService.#config = config; log.debug("Tenant service created successfully", { tenantId: tenant[0].id }); + + // TODO sent tenant admin invitation mail + return tenantService; } catch (error) { log.error("Failed to create tenant", {