feat(housekeeping): deliver content vertical
This commit is contained in:
1 parent
bdab924bdf
commit
fd68819d9b
67 files changed
+5742
-1916
No files matched your search
@@ -0,0 +1,168 @@
|
||||
// @ts-nocheck
|
||||
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 { createAd } from "./admin-ads";
|
||||
import { createArticle } from "./admin-articles";
|
||||
import { uploadMedia } from "./admin-media";
|
||||
import { saveFavicon } from "./save-favicon";
|
||||
|
||||
const { execute } = vi.hoisted(() => ({
|
||||
execute: vi.fn(async () => ({
|
||||
ok: true,
|
||||
data: { before: null, after: { id: "1" }, output: { url: "/api/media/x" } },
|
||||
correlationId: "legacy",
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("@/features/housekeeping/domains/content/services/mutations", () => ({
|
||||
contentMutationService: { execute },
|
||||
createContentMutationInvocation: (actor, correlationId) => ({
|
||||
expectedActorId: actor.id,
|
||||
correlationId,
|
||||
legacy: true,
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
|
||||
vi.mock("@/lib/safe-action", () => ({
|
||||
adminAction: (_options: unknown, handler: unknown) => handler,
|
||||
}));
|
||||
vi.mock("@/lib/safe-action-shared", () => ({
|
||||
ActionError: class ActionError extends Error {},
|
||||
actionOk: (data: unknown = {}) => ({ ok: true, data }),
|
||||
}));
|
||||
vi.mock("@/lib/logger", () => ({
|
||||
logger: { error: vi.fn() },
|
||||
}));
|
||||
vi.mock("@/lib/permissions", () => ({
|
||||
PERMS: {
|
||||
NEWS_EDIT: "news.edit",
|
||||
PAGES_EDIT: "pages.edit",
|
||||
SETTINGS_EDIT: "settings.edit",
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: {
|
||||
select: vi.fn(() => ({
|
||||
from: vi.fn(() => ({
|
||||
where: vi.fn(() => ({ limit: vi.fn(async () => []) })),
|
||||
})),
|
||||
})),
|
||||
insert: vi.fn(() => ({
|
||||
values: vi.fn(async () => [{ insertId: 1 }]),
|
||||
})),
|
||||
},
|
||||
WebsiteArticles: { id: "id", slug: "slug" },
|
||||
WebsiteAds: { id: "id" },
|
||||
WebsiteSetting: { key: "key" },
|
||||
}));
|
||||
vi.mock("@/lib/services/staff-activity", () => ({
|
||||
logStaffActivity: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/services/site-settings", () => ({
|
||||
siteSettings: { get: vi.fn(), reload: vi.fn() },
|
||||
}));
|
||||
vi.mock("@/lib/media-storage", () => ({
|
||||
MEDIA_ROOT: "C:\\media",
|
||||
resolveMediaPath: vi.fn((name: string) => `C:\\media\\${name}`),
|
||||
}));
|
||||
vi.mock("node:fs/promises", () => ({
|
||||
mkdir: vi.fn(),
|
||||
writeFile: vi.fn(),
|
||||
unlink: vi.fn(),
|
||||
}));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
|
||||
vi.mock("next/navigation", () => ({ redirect: vi.fn() }));
|
||||
|
||||
const staff = { id: 42, rank: 7, username: "operator" };
|
||||
const form = (data: Record<string, FormDataEntryValue>) => ({
|
||||
get: (key: string) => data[key] ?? null,
|
||||
has: (key: string) => key in data,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(requirePermission).mockResolvedValue(staff as never);
|
||||
execute.mockResolvedValue({
|
||||
ok: true,
|
||||
data: { before: null, after: { id: "1" }, output: { url: "/api/media/x" } },
|
||||
correlationId: "legacy",
|
||||
});
|
||||
});
|
||||
|
||||
describe("Content legacy wrappers", () => {
|
||||
it("delegates article creation and preserves redirect ordering", async () => {
|
||||
await createArticle(
|
||||
form({
|
||||
title: "Launch",
|
||||
shortStory: "Summary",
|
||||
fullStory: "Body",
|
||||
image: "/image.png",
|
||||
}) as FormData,
|
||||
);
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ expectedActorId: 42, legacy: true }),
|
||||
"article.change",
|
||||
expect.objectContaining({ action: "create", title: "Launch" }),
|
||||
);
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/articles");
|
||||
});
|
||||
|
||||
it("delegates ad creation and keeps the legacy void/redirect contract", async () => {
|
||||
expect(
|
||||
await createAd(form({ image: "https://example.test/ad.png" }) as FormData),
|
||||
).toBeUndefined();
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ expectedActorId: 42, legacy: true }),
|
||||
"ad.change",
|
||||
expect.objectContaining({ action: "create" }),
|
||||
);
|
||||
expect(redirect).toHaveBeenCalledWith("/admin/ads");
|
||||
});
|
||||
|
||||
it("delegates media and favicon uploads while retaining public result shapes", async () => {
|
||||
const file = new File(["bytes"], "image.png", { type: "image/png" });
|
||||
const media = await uploadMedia(form({ file }) as FormData);
|
||||
const favicon = await saveFavicon(form({ file }) as FormData);
|
||||
expect(media).toEqual({ ok: true });
|
||||
expect(favicon).toEqual({ success: true, url: "/api/media/x" });
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
"media.upload",
|
||||
expect.objectContaining({ file }),
|
||||
);
|
||||
expect(execute).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
"favicon.save",
|
||||
expect.objectContaining({ file }),
|
||||
);
|
||||
});
|
||||
|
||||
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-email-templates.ts",
|
||||
"src/actions/admin-help.ts",
|
||||
"src/actions/admin-media.ts",
|
||||
"src/actions/admin-nav-menu.ts",
|
||||
"src/actions/admin-photos.ts",
|
||||
"src/actions/admin-tags.ts",
|
||||
"src/actions/admin-theme.ts",
|
||||
"src/actions/admin-writeable-boxes.ts",
|
||||
"src/actions/banners.ts",
|
||||
"src/actions/events.ts",
|
||||
"src/actions/polls.ts",
|
||||
"src/actions/prefixes.ts",
|
||||
"src/actions/save-favicon.ts",
|
||||
"src/actions/save-logo.ts",
|
||||
"src/actions/translations.ts",
|
||||
"src/actions/emulator.ts",
|
||||
]) {
|
||||
expect(readFileSync(path, "utf8"), path).toContain(
|
||||
"contentMutationService",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user