mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-09-28 19:54:50 +02:00
Tenant creation, update and config functions
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
// Mock the database module
|
||||
@@ -106,32 +107,6 @@ describe("TenantAdminService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
it("should update tenant data", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const updateData = {
|
||||
longName: "Updated Clinic Name",
|
||||
description: "Updated description"
|
||||
};
|
||||
|
||||
const mockConfig = { setConfig: vi.fn() };
|
||||
const mockUpdateBuilder = {
|
||||
set: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockReturnThis()
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
mockCentralDb.update.mockReturnValue(mockUpdateBuilder);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
await service.update(updateData);
|
||||
|
||||
expect(mockCentralDb.update).toHaveBeenCalled();
|
||||
expect(mockUpdateBuilder.set).toHaveBeenCalledWith(updateData);
|
||||
expect(mockUpdateBuilder.where).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDb", () => {
|
||||
it("should return database connection", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
@@ -187,4 +162,127 @@ describe("TenantAdminService", () => {
|
||||
expect(config).toBe(mockConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateTenantData", () => {
|
||||
it("should update tenant data successfully", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const updateData = {
|
||||
longName: "Updated Clinic Name",
|
||||
description: "Updated description",
|
||||
logo: Buffer.from("logo data")
|
||||
};
|
||||
|
||||
const mockUpdatedTenant = {
|
||||
id: tenantId,
|
||||
shortName: "test-clinic",
|
||||
...updateData,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
const mockConfig = { setConfig: vi.fn() };
|
||||
const mockUpdateBuilder = {
|
||||
set: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockReturnThis(),
|
||||
returning: vi.fn().mockResolvedValue([mockUpdatedTenant])
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
mockCentralDb.update.mockReturnValue(mockUpdateBuilder);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
const result = await service.updateTenantData(updateData);
|
||||
|
||||
expect(mockCentralDb.update).toHaveBeenCalled();
|
||||
expect(mockUpdateBuilder.set).toHaveBeenCalledWith({
|
||||
...updateData,
|
||||
updatedAt: expect.any(Date)
|
||||
});
|
||||
expect(mockUpdateBuilder.where).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockUpdatedTenant);
|
||||
});
|
||||
|
||||
it("should throw NotFoundError when tenant not found", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const updateData = { longName: "Updated Name" };
|
||||
|
||||
const mockConfig = { setConfig: vi.fn() };
|
||||
const mockUpdateBuilder = {
|
||||
set: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockReturnThis(),
|
||||
returning: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
mockCentralDb.update.mockReturnValue(mockUpdateBuilder);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
|
||||
await expect(service.updateTenantData(updateData)).rejects.toThrow(
|
||||
"Tenant with ID tenant-123 not found"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateTenantConfig", () => {
|
||||
it("should update tenant configuration successfully", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const configUpdates = {
|
||||
brandColor: "#FF0000",
|
||||
maxChannels: 10,
|
||||
requireEmail: false
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
setConfig: vi.fn().mockResolvedValue(undefined)
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
const result = await service.updateTenantConfig(configUpdates);
|
||||
|
||||
expect(mockConfig.setConfig).toHaveBeenCalledTimes(3);
|
||||
expect(mockConfig.setConfig).toHaveBeenCalledWith("brandColor", "#FF0000");
|
||||
expect(mockConfig.setConfig).toHaveBeenCalledWith("maxChannels", 10);
|
||||
expect(mockConfig.setConfig).toHaveBeenCalledWith("requireEmail", false);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ key: "brandColor", value: "#FF0000" },
|
||||
{ key: "maxChannels", value: 10 },
|
||||
{ key: "requireEmail", value: false }
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle empty config updates", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const configUpdates = {};
|
||||
|
||||
const mockConfig = {
|
||||
setConfig: vi.fn().mockResolvedValue(undefined)
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
const result = await service.updateTenantConfig(configUpdates);
|
||||
|
||||
expect(mockConfig.setConfig).not.toHaveBeenCalled();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should propagate config errors", async () => {
|
||||
const tenantId = "tenant-123";
|
||||
const configUpdates = { brandColor: "#FF0000" };
|
||||
|
||||
const mockConfig = {
|
||||
setConfig: vi.fn().mockRejectedValue(new Error("Config error"))
|
||||
};
|
||||
|
||||
mockTenantConfig.create.mockResolvedValue(mockConfig);
|
||||
|
||||
const service = await TenantAdminService.getTenantById(tenantId);
|
||||
|
||||
await expect(service.updateTenantConfig(configUpdates)).rejects.toThrow("Config error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ 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";
|
||||
import { ValidationError, NotFoundError } from "../utils/errors";
|
||||
|
||||
if (!env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
|
||||
|
||||
@@ -89,6 +89,11 @@ export class TenantAdminService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tenant admin service by its ID
|
||||
* @param id
|
||||
* @returns new TenantAdminService
|
||||
*/
|
||||
static async getTenantById(id: string) {
|
||||
const log = logger.setContext("TenantAdminService");
|
||||
log.debug("Getting tenant by ID", { tenantId: id });
|
||||
@@ -105,31 +110,89 @@ export class TenantAdminService {
|
||||
}
|
||||
}
|
||||
|
||||
async update(updateData: Partial<Omit<centralSchema.InsertTenant, "id">>) {
|
||||
/**
|
||||
* Access the tenants configuration
|
||||
*/
|
||||
get configuration() {
|
||||
return this.#config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tenant data (longName, shortName, description, logo)
|
||||
*/
|
||||
async updateTenantData(
|
||||
updateData: Partial<Pick<InsertTenant, "longName" | "shortName" | "description" | "logo">>
|
||||
) {
|
||||
const log = logger.setContext("TenantAdminService");
|
||||
log.debug("Updating tenant", {
|
||||
log.debug("Updating tenant data", {
|
||||
tenantId: this.tenantId,
|
||||
updateFields: Object.keys(updateData)
|
||||
});
|
||||
|
||||
try {
|
||||
await centralDb
|
||||
const result = await centralDb
|
||||
.update(centralSchema.tenant)
|
||||
.set(updateData)
|
||||
.where(eq(centralSchema.tenant.id, this.tenantId));
|
||||
.set({
|
||||
...updateData,
|
||||
updatedAt: new Date()
|
||||
})
|
||||
.where(eq(centralSchema.tenant.id, this.tenantId))
|
||||
.returning();
|
||||
|
||||
log.debug("Tenant updated successfully", {
|
||||
if (!result[0]) {
|
||||
log.warn("Tenant update failed: Tenant not found", { tenantId: this.tenantId });
|
||||
throw new NotFoundError(`Tenant with ID ${this.tenantId} not found`);
|
||||
}
|
||||
|
||||
log.debug("Tenant data updated successfully", {
|
||||
tenantId: this.tenantId,
|
||||
updateFields: Object.keys(updateData)
|
||||
});
|
||||
|
||||
return result[0];
|
||||
} catch (error) {
|
||||
log.error("Failed to update tenant", { tenantId: this.tenantId, error: String(error) });
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
log.error("Failed to update tenant data", {
|
||||
tenantId: this.tenantId,
|
||||
error: String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
get configuration() {
|
||||
return this.#config;
|
||||
/**
|
||||
* Update tenant configuration entries using TenantConfig
|
||||
*/
|
||||
async updateTenantConfig(configUpdates: Record<string, boolean | number | string>) {
|
||||
const log = logger.setContext("TenantAdminService");
|
||||
log.debug("Updating tenant configuration", {
|
||||
tenantId: this.tenantId,
|
||||
configKeys: Object.keys(configUpdates)
|
||||
});
|
||||
|
||||
try {
|
||||
const results = [];
|
||||
|
||||
for (const [key, value] of Object.entries(configUpdates)) {
|
||||
await this.#config.setConfig(key, value);
|
||||
results.push({ key, value });
|
||||
}
|
||||
|
||||
log.debug("Tenant configuration updated successfully", {
|
||||
tenantId: this.tenantId,
|
||||
configKeys: Object.keys(configUpdates),
|
||||
updatedCount: results.length
|
||||
});
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
log.error("Failed to update tenant configuration", {
|
||||
tenantId: this.tenantId,
|
||||
configKeys: Object.keys(configUpdates),
|
||||
error: String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user