fix(housekeeping): address task 14 review round 1
This commit is contained in:
1 parent
fd68819d9b
commit
d09eaa33d6
23 files changed
+2503
-488
No files matched your search
@@ -2,11 +2,12 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { requirePermission, requireStaff } from "@/lib/admin/guard";
|
||||
import { createAd } from "./admin-ads";
|
||||
import { createArticle } from "./admin-articles";
|
||||
import { uploadMedia } from "./admin-media";
|
||||
import { saveFavicon } from "./save-favicon";
|
||||
import { deleteFavicon, saveFavicon } from "./save-favicon";
|
||||
import { saveLogo } from "./save-logo";
|
||||
|
||||
const { execute } = vi.hoisted(() => ({
|
||||
execute: vi.fn(async () => ({
|
||||
@@ -15,6 +16,13 @@ const { execute } = vi.hoisted(() => ({
|
||||
correlationId: "legacy",
|
||||
})),
|
||||
}));
|
||||
const { executeLegacyBrandAssetMutation } = vi.hoisted(() => ({
|
||||
executeLegacyBrandAssetMutation: vi.fn(async () => ({
|
||||
before: null,
|
||||
after: { value: "/api/media/x" },
|
||||
output: { url: "/api/media/x" },
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("@/features/housekeeping/domains/content/services/mutations", () => ({
|
||||
contentMutationService: { execute },
|
||||
@@ -24,7 +32,16 @@ vi.mock("@/features/housekeeping/domains/content/services/mutations", () => ({
|
||||
legacy: true,
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock(
|
||||
"@/features/housekeeping/domains/content/services/mutation-runtime-external",
|
||||
() => ({
|
||||
executeLegacyBrandAssetMutation,
|
||||
}),
|
||||
);
|
||||
vi.mock("@/lib/admin/guard", () => ({
|
||||
requirePermission: vi.fn(),
|
||||
requireStaff: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/safe-action", () => ({
|
||||
adminAction: (_options: unknown, handler: unknown) => handler,
|
||||
}));
|
||||
@@ -40,6 +57,7 @@ vi.mock("@/lib/permissions", () => ({
|
||||
NEWS_EDIT: "news.edit",
|
||||
PAGES_EDIT: "pages.edit",
|
||||
SETTINGS_EDIT: "settings.edit",
|
||||
SETTINGS_VIEW: "settings.view",
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/db", () => ({
|
||||
@@ -84,6 +102,7 @@ const form = (data: Record<string, FormDataEntryValue>) => ({
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
vi.mocked(requireStaff).mockResolvedValue(staff as never);
|
||||
execute.mockResolvedValue({
|
||||
ok: true,
|
||||
data: { before: null, after: { id: "1" }, output: { url: "/api/media/x" } },
|
||||
@@ -111,7 +130,9 @@ describe("Content legacy wrappers", () => {
|
||||
|
||||
it("delegates ad creation and keeps the legacy void/redirect contract", async () => {
|
||||
expect(
|
||||
await createAd(form({ image: "https://example.test/ad.png" }) as FormData),
|
||||
await createAd(
|
||||
form({ image: "https://example.test/ad.png" }) as FormData,
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ expectedActorId: 42, legacy: true }),
|
||||
@@ -132,17 +153,51 @@ describe("Content legacy wrappers", () => {
|
||||
"media.upload",
|
||||
expect.objectContaining({ file }),
|
||||
);
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect(executeLegacyBrandAssetMutation).toHaveBeenCalledWith(
|
||||
"favicon.save",
|
||||
expect.objectContaining({ file }),
|
||||
{ file },
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the legacy favicon page gate and establishes a staff logo floor", async () => {
|
||||
vi.clearAllMocks();
|
||||
const file = new File(["bytes"], "image.png", { type: "image/png" });
|
||||
await saveFavicon(form({ file }) as FormData);
|
||||
await deleteFavicon();
|
||||
await saveLogo(form({ file }) as FormData);
|
||||
expect(requirePermission).toHaveBeenNthCalledWith(1, "settings.view");
|
||||
expect(requirePermission).toHaveBeenNthCalledWith(2, "settings.view");
|
||||
expect(requirePermission).not.toHaveBeenCalledWith("settings.edit");
|
||||
expect(requireStaff).toHaveBeenCalledOnce();
|
||||
expect(
|
||||
executeLegacyBrandAssetMutation.mock.calls.map(
|
||||
([operation]) => operation,
|
||||
),
|
||||
).toEqual(["favicon.save", "favicon.delete", "logo.save"]);
|
||||
});
|
||||
|
||||
it("does not mutate brand assets when either legacy guard denies access", async () => {
|
||||
const file = new File(["bytes"], "image.png", { type: "image/png" });
|
||||
vi.mocked(requirePermission).mockRejectedValueOnce(
|
||||
new Error("favicon denied"),
|
||||
);
|
||||
await expect(saveFavicon(form({ file }) as FormData)).rejects.toThrow(
|
||||
"favicon denied",
|
||||
);
|
||||
expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled();
|
||||
|
||||
vi.mocked(requireStaff).mockRejectedValueOnce(new Error("logo denied"));
|
||||
await expect(saveLogo(form({ file }) as FormData)).rejects.toThrow(
|
||||
"logo denied",
|
||||
);
|
||||
expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps every listed legacy action as a thin shared-service wrapper", () => {
|
||||
for (const path of [
|
||||
"src/actions/admin-ads.ts",
|
||||
"src/actions/admin-articles.ts",
|
||||
"src/actions/admin-banners.ts",
|
||||
"src/actions/admin-email-templates.ts",
|
||||
"src/actions/admin-help.ts",
|
||||
"src/actions/admin-media.ts",
|
||||
@@ -160,9 +215,13 @@ describe("Content legacy wrappers", () => {
|
||||
"src/actions/translations.ts",
|
||||
"src/actions/emulator.ts",
|
||||
]) {
|
||||
expect(readFileSync(path, "utf8"), path).toContain(
|
||||
"contentMutationService",
|
||||
);
|
||||
const source = readFileSync(path, "utf8");
|
||||
expect(
|
||||
source.includes("contentMutationService") ||
|
||||
source.includes("executeLegacyBrandAssetMutation") ||
|
||||
source.includes('from "./banners"'),
|
||||
path,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user