Files
gunshiz 9580407a90
CI / Verify (push) Successful in 1m56s
CI / Build immutable images and deploy (push) Successful in 3m12s
perf : cache
2026-10-08 23:21:34 +07:00

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();
});
});