58 lines
3.2 KiB
TypeScript
58 lines
3.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({ admin: vi.fn(), updateTag: vi.fn(), save: vi.fn() }));
|
|
vi.mock("next/cache", () => ({ updateTag: mocks.updateTag }));
|
|
vi.mock("@/lib/auth/server", () => ({ requireAdmin: mocks.admin }));
|
|
vi.mock("@/lib/audit-log", () => ({ auditActor: () => ({ id: "admin", label: "Admin" }) }));
|
|
vi.mock("@/lib/glossary-repository", () => ({ createGlossaryAlias: vi.fn(), deleteGlossaryAlias: vi.fn() }));
|
|
vi.mock("@/lib/catalog/sync", () => ({ cancelCatalogSyncJob: vi.fn(), getCatalogSyncJob: vi.fn(), startCatalogSyncJob: vi.fn() }));
|
|
vi.mock("@/lib/guides/queries", () => ({
|
|
getAdminGuideContentById: vi.fn(), getAdminTeamImportGuide: vi.fn(),
|
|
listAdminTeamImportsForCharacter: vi.fn(), listAdminTeamImportGuides: vi.fn(),
|
|
}));
|
|
vi.mock("@/lib/guides/mutations", () => ({
|
|
createExtraSection: mocks.save, createGuide: mocks.save, deleteExtraSection: mocks.save,
|
|
permanentlyDeleteGuide: mocks.save, reorderExtraSections: mocks.save, restoreGuide: mocks.save,
|
|
saveArtifacts: mocks.save, saveConstellations: mocks.save, saveExtraSection: mocks.save,
|
|
saveTeams: mocks.save, setGuideVisibility: mocks.save, saveWeapons: mocks.save,
|
|
trashGuide: mocks.save, updateGuideBasics: mocks.save,
|
|
GuideVersionConflictError: class extends Error { currentVersion = 9; },
|
|
}));
|
|
|
|
import { moveGuideToTrash, restoreStructuredGuide, saveArtifactSection, saveGuideExtra, saveGuideOverview, saveWeaponSection } from "./actions";
|
|
import { GuideVersionConflictError } from "@/lib/guides/mutations";
|
|
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
mocks.admin.mockResolvedValue({ user: { id: "admin" } });
|
|
mocks.save.mockResolvedValue({ version: 10 });
|
|
});
|
|
|
|
describe("guide save cache freshness", () => {
|
|
const guideId = "550e8400-e29b-41d4-a716-446655440000";
|
|
it.each([saveArtifactSection, saveGuideOverview, saveWeaponSection, moveGuideToTrash, restoreStructuredGuide])(
|
|
"expires the public page immediately after a successful action", async (action) => {
|
|
expect(await action(guideId, {})).toMatchObject({ status: "saved", version: 10 });
|
|
expect(mocks.updateTag).toHaveBeenCalledWith(`buzz:page:${guideId}`);
|
|
},
|
|
);
|
|
it("invalidates custom extras without changing their section id or version input", async () => {
|
|
const sectionId = "550e8400-e29b-41d4-a716-446655440001";
|
|
const input = { expectedVersion: 9 };
|
|
await saveGuideExtra(guideId, sectionId, input);
|
|
expect(mocks.save).toHaveBeenCalledWith(guideId, sectionId, input, expect.any(Object));
|
|
expect(mocks.updateTag).toHaveBeenCalledWith(`buzz:page:${guideId}`);
|
|
});
|
|
it("preserves conflicts without invalidating or reporting a save", async () => {
|
|
mocks.save.mockRejectedValue(new GuideVersionConflictError(9));
|
|
expect(await saveArtifactSection(guideId, {})).toMatchObject({ status: "conflict", currentVersion: 9 });
|
|
expect(mocks.updateTag).not.toHaveBeenCalled();
|
|
});
|
|
it("authorizes before writes or cache invalidation", async () => {
|
|
mocks.admin.mockRejectedValue(new Error("unauthorized"));
|
|
await expect(saveArtifactSection(guideId, {})).rejects.toThrow("unauthorized");
|
|
expect(mocks.save).not.toHaveBeenCalled();
|
|
expect(mocks.updateTag).not.toHaveBeenCalled();
|
|
});
|
|
});
|