Moved unit tests. createTenant now reflects UI prototype.

This commit is contained in:
Hendrik Belitz
2025-07-09 14:05:05 +02:00
parent b48f684952
commit 8ee744e3be
12 changed files with 67 additions and 75 deletions
+1
View File
@@ -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"
@@ -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", () => ({
@@ -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()", () => {
@@ -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();
@@ -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);
@@ -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: áéíóú 中文 🚀";
+1
View File
@@ -5,6 +5,7 @@ export type JsonSchema = {
required?: string[];
format?: string;
description?: string;
default?: unknown;
example?: unknown;
enum?: unknown[];
$ref?: string;
@@ -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");
@@ -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"
};
@@ -13,7 +13,7 @@ export type InsertAdminPasskey = InferInsertModel<typeof centralSchema.adminPass
const adminCreationSchema = z.object({
name: z.string().min(5),
email: z.string().email(),
email: z.email(),
token: z.uuidv7().optional(),
tokenValidUntil: z.date().optional()
});
@@ -32,7 +32,7 @@ export class AdminAccountService {
if (!validated.success) {
log.warn("Admin creation failed: Invalid data", {
email: adminData.email,
errors: validated.error.errors
errors: validated.error
});
throw new ValidationError("Invalid admin data");
}
@@ -1,24 +1,38 @@
import { centralDb, getTenantDb } from "../db";
import * as centralSchema from "../db/central-schema";
import { type InsertTenant } from "../db/central-schema";
import { TenantConfig } from "../db/tenant-config";
import { env } from "$env/dynamic/private";
import { eq } from "drizzle-orm";
import logger from "$lib/logger";
import z from "zod/v4";
import { ValidationError } from "../utils/errors";
if (!env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
const tentantCreationSchema = z.object({
shortName: z.string().min(4).max(15),
inviteAdmin: z.email().optional()
});
export type TenantCreationRequest = z.infer<typeof tentantCreationSchema>;
export class TenantAdminService {
#config!: TenantConfig;
#db: Awaited<ReturnType<typeof getTenantDb>> | 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<string, boolean | number | string> = {
@@ -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", {