mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 21:41:17 +00:00
see comments
This commit is contained in:
parent
a26e2a6245
commit
94e632681e
4 changed files with 98 additions and 51 deletions
|
|
@ -4,9 +4,8 @@ vi.mock("@/src/lib/prisma", () => ({
|
|||
prisma: {
|
||||
course: {
|
||||
findMany: vi.fn(),
|
||||
updateMany: vi.fn(),
|
||||
updateManyAndReturn: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -14,8 +13,10 @@ vi.mock("@/src/lib/auth/getUser", () => ({
|
|||
getUserData: vi.fn(),
|
||||
isManager: (u: { role: string }) => u?.role === "manager",
|
||||
isEmployee: (u: { role: string }) => u?.role === "employee",
|
||||
getEffectiveManagerId: (u: { role: string; id: string; managerId?: string }) =>
|
||||
u?.role === "manager" ? u.id : (u?.managerId ?? null),
|
||||
// Spiegelt die echte Implementierung: für Employees ist der Manager
|
||||
// EmployeeData.createdBy — nicht etwa ein managerId-Feld, das es nicht gibt.
|
||||
getEffectiveManagerId: (u: { role: string; id: string; createdBy?: string | null }) =>
|
||||
u?.role === "manager" ? u.id : (u?.createdBy ?? null),
|
||||
}));
|
||||
|
||||
vi.mock("next/cache", () => ({
|
||||
|
|
@ -30,8 +31,7 @@ import {
|
|||
} from "@/src/modules/courses/actions/publish-drafts-actions";
|
||||
|
||||
const findMany = vi.mocked(prisma.course.findMany);
|
||||
const updateMany = vi.mocked(prisma.course.updateMany);
|
||||
const transaction = vi.mocked(prisma.$transaction);
|
||||
const updateManyAndReturn = vi.mocked(prisma.course.updateManyAndReturn);
|
||||
const mockGetUserData = vi.mocked(getUserData);
|
||||
|
||||
const MANAGER = { id: "mgr-1", role: "manager" };
|
||||
|
|
@ -61,17 +61,13 @@ const completeDraft = (overrides: Record<string, unknown> = {}) => ({
|
|||
...overrides,
|
||||
});
|
||||
|
||||
/** $transaction mit Callback ausführen, als liefe sie gegen die echte DB. */
|
||||
const runTransaction = (updatedIds: string[]) => {
|
||||
transaction.mockImplementationOnce(async (cb: unknown) => {
|
||||
const tx = {
|
||||
course: {
|
||||
updateMany: vi.fn().mockResolvedValue({ count: updatedIds.length }),
|
||||
findMany: vi.fn().mockResolvedValue(updatedIds.map((id) => ({ id }))),
|
||||
},
|
||||
};
|
||||
return (cb as (t: typeof tx) => Promise<string[]>)(tx);
|
||||
});
|
||||
/**
|
||||
* Simuliert updateManyAndReturn: die DB gibt nur die Zeilen zurück, die dieses
|
||||
* Statement tatsächlich geändert hat. Wer nicht in updatedIds steht, wurde
|
||||
* zwischenzeitlich von anderer Seite verändert und bleibt unberührt.
|
||||
*/
|
||||
const mockUpdateReturning = (updatedIds: string[]) => {
|
||||
updateManyAndReturn.mockResolvedValueOnce(updatedIds.map((id) => ({ id })) as never);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -90,7 +86,7 @@ describe("getPublishableDrafts", () => {
|
|||
mockGetUserData.mockResolvedValueOnce({
|
||||
id: "emp-1",
|
||||
role: "employee",
|
||||
managerId: "mgr-1",
|
||||
createdBy: "mgr-1",
|
||||
permissions: { courses: { edit: false } },
|
||||
} as never);
|
||||
|
||||
|
|
@ -185,7 +181,7 @@ describe("publishDraftCourses", () => {
|
|||
mockGetUserData.mockResolvedValueOnce(null as never);
|
||||
const result = await publishDraftCourses(["c1"]);
|
||||
expect(result.success).toBe(false);
|
||||
expect(transaction).not.toHaveBeenCalled();
|
||||
expect(updateManyAndReturn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lehnt eine leere Auswahl ab", async () => {
|
||||
|
|
@ -212,7 +208,7 @@ describe("publishDraftCourses", () => {
|
|||
it("veröffentlicht vollständige Entwürfe", async () => {
|
||||
mockGetUserData.mockResolvedValueOnce(MANAGER as never);
|
||||
findMany.mockResolvedValueOnce([completeDraft()] as never);
|
||||
runTransaction(["c1"]);
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
const result = await publishDraftCourses(["c1"]);
|
||||
|
||||
|
|
@ -229,7 +225,7 @@ describe("publishDraftCourses", () => {
|
|||
completeDraft(),
|
||||
completeDraft({ id: "c2", name: "Zumba", room: "" }),
|
||||
] as never);
|
||||
runTransaction(["c1"]);
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
const result = await publishDraftCourses(["c1", "c2"]);
|
||||
|
||||
|
|
@ -242,10 +238,47 @@ describe("publishDraftCourses", () => {
|
|||
expect(result.skipped[0].reason).toContain("Raum");
|
||||
});
|
||||
|
||||
it("meldet nur Kurse als veröffentlicht, die dieses Update wirklich geändert hat", async () => {
|
||||
mockGetUserData.mockResolvedValueOnce(MANAGER as never);
|
||||
findMany.mockResolvedValueOnce([
|
||||
completeDraft(),
|
||||
completeDraft({ id: "c2", name: "Pilates" }),
|
||||
] as never);
|
||||
// c2 wurde zwischen Prüfung und Update von anderer Seite verändert (z.B.
|
||||
// parallel veröffentlicht oder abgesagt) — das Update erfasst nur c1.
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
const result = await publishDraftCourses(["c1", "c2"]);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (!result.success) return;
|
||||
expect(result.published).toBe(1);
|
||||
expect(result.publishedIds).toEqual(["c1"]);
|
||||
// c2 darf weder in publishedIds stehen noch mitgezählt werden.
|
||||
expect(result.publishedIds).not.toContain("c2");
|
||||
expect(result.vanished).toBe(1);
|
||||
});
|
||||
|
||||
it("veröffentlicht nur Kurse, die noch den Status draft haben", async () => {
|
||||
mockGetUserData.mockResolvedValueOnce(MANAGER as never);
|
||||
findMany.mockResolvedValueOnce([completeDraft()] as never);
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
await publishDraftCourses(["c1"]);
|
||||
|
||||
// Ohne status: "draft" im where könnte ein zwischenzeitlich abgesagter
|
||||
// Kurs wieder live gehen.
|
||||
expect(updateManyAndReturn.mock.calls[0][0]?.where).toEqual({
|
||||
id: { in: ["c1"] },
|
||||
createdBy: "mgr-1",
|
||||
status: "draft",
|
||||
});
|
||||
});
|
||||
|
||||
it("zählt IDs, die die Abfrage nicht zurückgibt, als verschwunden", async () => {
|
||||
mockGetUserData.mockResolvedValueOnce(MANAGER as never);
|
||||
findMany.mockResolvedValueOnce([completeDraft()] as never);
|
||||
runTransaction(["c1"]);
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
const result = await publishDraftCourses(["c1", "geloescht", "fremd"]);
|
||||
|
||||
|
|
@ -263,19 +296,18 @@ describe("publishDraftCourses", () => {
|
|||
expect(result.success).toBe(true);
|
||||
if (!result.success) return;
|
||||
expect(result.published).toBe(0);
|
||||
expect(transaction).not.toHaveBeenCalled();
|
||||
expect(updateMany).not.toHaveBeenCalled();
|
||||
expect(updateManyAndReturn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("erlaubt Employees mit edit-Berechtigung das Veröffentlichen im Namen des Managers", async () => {
|
||||
mockGetUserData.mockResolvedValueOnce({
|
||||
id: "emp-1",
|
||||
role: "employee",
|
||||
managerId: "mgr-1",
|
||||
createdBy: "mgr-1",
|
||||
permissions: { courses: { edit: true } },
|
||||
} as never);
|
||||
findMany.mockResolvedValueOnce([completeDraft()] as never);
|
||||
runTransaction(["c1"]);
|
||||
mockUpdateReturning(["c1"]);
|
||||
|
||||
const result = await publishDraftCourses(["c1"]);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue