mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Moved unit tests. createTenant now reflects UI prototype.
This commit is contained in:
@@ -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()", () => {
|
||||
+11
-10
@@ -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: áéíóú 中文 🚀";
|
||||
@@ -5,6 +5,7 @@ export type JsonSchema = {
|
||||
required?: string[];
|
||||
format?: string;
|
||||
description?: string;
|
||||
default?: unknown;
|
||||
example?: unknown;
|
||||
enum?: unknown[];
|
||||
$ref?: string;
|
||||
|
||||
+6
-35
@@ -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");
|
||||
+8
-9
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user