Files
app/src/__tests__/planCategory.test.ts
BOHA 4d47897e27 feat(plan): add hard-delete for categories (blocked when in use)
DELETE /plan/categories/:id now hard-deletes the row, guarded: refuses when any live entry/override uses the key (suggests hide instead) and keeps >=1 active category. Modal gains a per-row Smazat with inline confirm. Hiding stays via PATCH is_active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:12:40 +02:00

130 lines
4.1 KiB
TypeScript

import { describe, it, expect, afterAll } from "vitest";
import prisma from "../config/database";
import {
listPlanCategories,
createPlanCategory,
updatePlanCategory,
deactivatePlanCategory,
deletePlanCategory,
isCategoryKeyActive,
slugifyCategory,
} from "../services/planCategory.service";
const created: number[] = [];
afterAll(async () => {
if (created.length) {
await prisma.plan_categories.deleteMany({ where: { id: { in: created } } });
}
await prisma.$disconnect();
});
describe("slugifyCategory", () => {
it("strips Czech diacritics and non-alphanumerics", () => {
expect(slugifyCategory("Cesta / Montáž")).toBe("cesta_montaz");
expect(slugifyCategory("Příprava")).toBe("priprava");
});
});
describe("plan category service", () => {
it("seeds are present and ordered", async () => {
const cats = await listPlanCategories(true);
const keys = cats.map((c) => c.key);
expect(keys).toContain("work");
expect(keys).toContain("other");
});
it("creates a category with a unique slug and next sort_order", async () => {
const res = await createPlanCategory({
label: "Test Kategorie ZZ",
color: "#123456",
});
if ("error" in res) throw new Error(res.error);
created.push(res.data.id);
expect(res.data.key).toBe("test_kategorie_zz");
expect(res.data.color).toBe("#123456");
expect(res.data.is_active).toBe(true);
expect(await isCategoryKeyActive("test_kategorie_zz")).toBe(true);
});
it("dedupes a colliding slug", async () => {
const res = await createPlanCategory({
label: "Test Kategorie ZZ",
color: "#222222",
});
if ("error" in res) throw new Error(res.error);
created.push(res.data.id);
expect(res.data.key).toBe("test_kategorie_zz_2");
});
it("updates label and color (key immutable)", async () => {
const res = await createPlanCategory({
label: "Test Upd ZZ",
color: "#aaaaaa",
});
if ("error" in res) throw new Error(res.error);
created.push(res.data.id);
const upd = await updatePlanCategory(res.data.id, {
label: "Test Upd2 ZZ",
color: "#bbbbbb",
});
if ("error" in upd) throw new Error(upd.error);
expect(upd.data.label).toBe("Test Upd2 ZZ");
expect(upd.data.color).toBe("#bbbbbb");
expect(upd.data.key).toBe("test_upd_zz");
});
it("deactivate hides the key from active check", async () => {
const res = await createPlanCategory({
label: "Test Deact ZZ",
color: "#cccccc",
});
if ("error" in res) throw new Error(res.error);
created.push(res.data.id);
await deactivatePlanCategory(res.data.id);
expect(await isCategoryKeyActive("test_deact_zz")).toBe(false);
});
it("hard-deletes an unused category", async () => {
const res = await createPlanCategory({
label: "Test Del ZZ",
color: "#dddddd",
});
if ("error" in res) throw new Error(res.error);
const del = await deletePlanCategory(res.data.id);
expect("data" in del).toBe(true);
const stillThere = await prisma.plan_categories.findUnique({
where: { id: res.data.id },
});
expect(stillThere).toBeNull();
});
it("refuses to hard-delete a category in use", async () => {
const res = await createPlanCategory({
label: "Test InUse ZZ",
color: "#eeeeee",
});
if ("error" in res) throw new Error(res.error);
created.push(res.data.id);
const user = await prisma.users.findFirst({ select: { id: true } });
if (!user) throw new Error("no user in test DB");
const entry = await prisma.work_plan_entries.create({
data: {
user_id: user.id,
date_from: new Date("2026-06-10T00:00:00.000Z"),
date_to: new Date("2026-06-10T00:00:00.000Z"),
category: res.data.key,
note: "test in-use",
created_by: user.id,
},
});
try {
const del = await deletePlanCategory(res.data.id);
expect("error" in del).toBe(true);
if ("error" in del) expect(del.status).toBe(409);
} finally {
await prisma.work_plan_entries.delete({ where: { id: entry.id } });
}
});
});