Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/tenantService.test.ts
T

82 lines
3.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import {
getAvailableTenantSlug,
isReservedTenantChildRouteSlug,
isReservedTenantSlug,
normalizeTenantSlug,
validateDesiredTenantSlug,
} from "./tenantService";
import { db } from "../db.server";
vi.mock("../db.server", () => ({ db: { tenant: { count: vi.fn() } } }));
const countMock = vi.mocked(db.tenant.count);
vi.mock("../stripe.server", () => ({ deleteStripeCustomer: vi.fn() }));
vi.mock("../db/tenants.db.server", () => ({ deleteTenant: vi.fn() }));
vi.mock("../db/tenantSubscriptions.db.server", () => ({ getTenantSubscription: vi.fn() }));
vi.mock("./.server/subscriptionService", () => ({ cancelTenantSubscription: vi.fn() }));
describe("tenant route slug reservations", () => {
it("normalizes slugs before checking top-level route reservations", () => {
expect(normalizeTenantSlug(" Settings ")).toBe("settings");
expect(isReservedTenantSlug("Admin")).toBe(true);
expect(isReservedTenantSlug("Acme")).toBe(false);
});
it("reserves tenant child routes used by canonical workspace pages", () => {
expect(isReservedTenantChildRouteSlug("settings")).toBe(true);
expect(isReservedTenantChildRouteSlug("CRM")).toBe(true);
expect(isReservedTenantChildRouteSlug("g")).toBe(true);
expect(isReservedTenantChildRouteSlug("customers")).toBe(false);
});
});
describe("validateDesiredTenantSlug", () => {
it("accepts a normal workspace name and returns the normalized slug", () => {
expect(validateDesiredTenantSlug("Acme Corp")).toEqual({ ok: true, slug: "acme-corp" });
expect(validateDesiredTenantSlug(" Beta Works ")).toEqual({ ok: true, slug: "beta-works" });
});
it("rejects reserved slugs with a typed reason", () => {
expect(validateDesiredTenantSlug("admin")).toEqual({ ok: false, reason: "reserved" });
expect(validateDesiredTenantSlug("API")).toEqual({ ok: false, reason: "reserved" });
expect(validateDesiredTenantSlug("settings")).toEqual({ ok: false, reason: "reserved" });
});
it("rejects empty and symbol-only input", () => {
expect(validateDesiredTenantSlug("")).toEqual({ ok: false, reason: "empty" });
expect(validateDesiredTenantSlug("!!!")).toEqual({ ok: false, reason: "empty" });
});
it("rejects single-character slugs as too short", () => {
expect(validateDesiredTenantSlug("x")).toEqual({ ok: false, reason: "too-short" });
});
});
describe("getAvailableTenantSlug", () => {
beforeEach(() => {
countMock.mockReset();
});
it("returns the name-derived slug when free", async () => {
countMock.mockResolvedValue(0);
await expect(getAvailableTenantSlug({ name: "Acme" })).resolves.toBe("acme");
});
it("appends a counter when the slug is already taken", async () => {
countMock.mockResolvedValueOnce(1).mockResolvedValueOnce(0);
await expect(getAvailableTenantSlug({ name: "Acme" })).resolves.toBe("acme1");
});
it("skips reserved desired slugs without a DB lookup and falls back to the name base", async () => {
countMock.mockResolvedValue(0);
// "admin" ist reserviert -> getExistingSlug kurzschliesst (kein count) -> Namens-Basis + Zaehler.
await expect(getAvailableTenantSlug({ name: "Acme", slug: "admin" })).resolves.toBe("acme1");
});
it("never returns an empty or numeric slug for symbol-only names", async () => {
countMock.mockResolvedValue(0);
await expect(getAvailableTenantSlug({ name: "!!!" })).resolves.toBe("workspace");
});
});