Files
sass-mohfarawati/tests/integration/actions-portfolio.test.ts
T
Moh e2e06be86e test: add comprehensive automated test coverage
- Vitest multi-project setup (unit / integration / component)
- Real-Postgres integration harness via in-process PGlite (TEST_DATABASE_URL
  override), migrations applied per worker; production code untouched
- Unit: routing, locale, validation/Zod schemas, metadata, mail, site-theme,
  marquee, media, portfolio helpers, plus architecture-rule tests
- Integration: Prisma data layer, API routes, and all server actions
- Component: UI primitives and form components (jsdom + Testing Library)
- 371 tests passing
2026-08-06 02:27:20 +02:00

223 lines
8.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("next/cache", async () => ({ revalidatePath: (await import("@/tests/helpers/next-mocks")).revalidatePath }));
vi.mock("next/navigation", async () => ({ redirect: (await import("@/tests/helpers/next-mocks")).redirect }));
vi.mock("next/dist/client/components/redirect-error", async () => ({
isRedirectError: (await import("@/tests/helpers/next-mocks")).isRedirectError,
}));
vi.mock("@/lib/admin-auth", async () => {
const m = await import("@/tests/helpers/next-mocks");
return { isAdminAuthenticated: m.isAdminAuthenticated, clearAdminSessionCookie: m.clearAdminSessionCookie };
});
import {
deleteCategoryAction,
deleteProjectAction,
saveProjectAction,
upsertCategoryAction,
} from "@/app/_admin/portfolio/actions";
import { prisma } from "@/lib/prisma";
import { createCategory, createProject } from "@/tests/helpers/factories";
import { adminAuth, captureRedirect, formDataFrom, resetNextMocks } from "@/tests/helpers/next-mocks";
beforeEach(() => {
resetNextMocks();
});
function categoryForm(overrides: Record<string, string> = {}) {
return formDataFrom({
slug: "branding",
nameAr: "الهوية",
nameEn: "Branding",
nameDe: "Branding",
descriptionAr: "وصف",
descriptionEn: "Description",
descriptionDe: "Beschreibung",
sortOrder: "1",
isActive: "on",
...overrides,
});
}
function projectForm(categoryId: string, overrides: Record<string, string> = {}) {
const assets = JSON.stringify([
{
kind: "IMAGE",
altAr: "ع",
altEn: "Alt",
altDe: "Alt",
sortOrder: 0,
media: { mode: "external", url: "https://cdn/asset.png", kind: "IMAGE", label: "Asset" },
},
]);
const coverMedia = JSON.stringify({ mode: "external", url: "https://cdn/cover.png", kind: "IMAGE", label: "Cover" });
return formDataFrom({
categoryId,
slug: "case-study",
viewMode: "GRID",
titleAr: "عنوان",
titleEn: "Title",
titleDe: "Titel",
summaryAr: "ملخص",
summaryEn: "Summary",
summaryDe: "Zusammenfassung",
clientName: "Client",
projectYear: "2025",
serviceLabelAr: "خدمة",
serviceLabelEn: "Service",
serviceLabelDe: "Service",
previewUrl: "https://example.com",
sortOrder: "0",
isFeatured: "",
isPublished: "on",
sections: "[]",
assets,
coverMedia,
...overrides,
});
}
describe("upsertCategoryAction", () => {
it("creates a category", async () => {
const url = await captureRedirect(() => upsertCategoryAction(categoryForm()));
expect(url).toContain("success=");
const category = await prisma.category.findUnique({ where: { slug: "branding" } });
expect(category?.nameEn).toBe("Branding");
expect(category?.isActive).toBe(true);
});
it("updates an existing category", async () => {
const existing = await createCategory({ slug: "old", nameEn: "Old" });
const url = await captureRedirect(() =>
upsertCategoryAction(categoryForm({ id: existing.id, slug: "old", nameEn: "Renamed" })),
);
expect(url).toContain("success=");
const category = await prisma.category.findUnique({ where: { id: existing.id } });
expect(category?.nameEn).toBe("Renamed");
});
it("reports a unique-constraint violation on duplicate slugs", async () => {
await createCategory({ slug: "branding" });
const url = await captureRedirect(() => upsertCategoryAction(categoryForm({ slug: "branding" })));
expect(url).toContain("error=");
expect(decodeURIComponent(url)).toContain("eindeutig");
});
it("reports validation errors for an invalid slug", async () => {
const url = await captureRedirect(() => upsertCategoryAction(categoryForm({ slug: "Not Valid" })));
expect(url).toContain("error=");
});
it("redirects unauthenticated callers to the admin root", async () => {
adminAuth.authenticated = false;
const url = await captureRedirect(() => upsertCategoryAction(categoryForm()));
expect(url).toBe("/");
});
});
describe("deleteCategoryAction", () => {
it("refuses to delete a category that has projects", async () => {
const category = await createCategory();
await createProject({ categoryId: category.id });
const url = await captureRedirect(() => deleteCategoryAction(formDataFrom({ id: category.id })));
expect(url).toContain("error=");
expect(await prisma.category.findUnique({ where: { id: category.id } })).not.toBeNull();
});
it("deletes an empty category", async () => {
const category = await createCategory();
const url = await captureRedirect(() => deleteCategoryAction(formDataFrom({ id: category.id })));
expect(url).toContain("success=");
expect(await prisma.category.findUnique({ where: { id: category.id } })).toBeNull();
});
});
describe("saveProjectAction", () => {
it("creates a published project with cover and asset media usages", async () => {
const category = await createCategory();
const url = await captureRedirect(() => saveProjectAction(projectForm(category.id)));
expect(url).toContain("success=");
const project = await prisma.portfolioProject.findUnique({ where: { slug: "case-study" } });
expect(project).not.toBeNull();
expect(project?.isPublished).toBe(true);
expect(project?.publishedAt).not.toBeNull();
expect(project?.coverImagePath).toBe("https://cdn/cover.png");
expect(await prisma.portfolioAsset.count({ where: { projectId: project!.id } })).toBe(1);
const usages = await prisma.mediaUsage.findMany({
where: { entityType: "portfolio-project", entityId: project!.id },
});
const usageTypes = usages.map((u) => u.usageType).sort();
expect(usageTypes).toEqual(["PORTFOLIO_ASSET", "PORTFOLIO_COVER"]);
});
it("updates an existing project and replaces its assets", async () => {
const category = await createCategory();
const created = await captureRedirect(() => saveProjectAction(projectForm(category.id)));
void created;
const project = await prisma.portfolioProject.findUnique({ where: { slug: "case-study" } });
const url = await captureRedirect(() =>
saveProjectAction(projectForm(category.id, { id: project!.id, titleEn: "Updated Title" })),
);
expect(url).toContain("success=");
const updated = await prisma.portfolioProject.findUnique({ where: { id: project!.id } });
expect(updated?.titleEn).toBe("Updated Title");
// assets are replaced, not duplicated
expect(await prisma.portfolioAsset.count({ where: { projectId: project!.id } })).toBe(1);
});
it("keeps the original publishedAt when re-saving an already published project", async () => {
const category = await createCategory();
await captureRedirect(() => saveProjectAction(projectForm(category.id)));
const first = await prisma.portfolioProject.findUnique({ where: { slug: "case-study" } });
const originalPublishedAt = first!.publishedAt;
await captureRedirect(() => saveProjectAction(projectForm(category.id, { id: first!.id })));
const second = await prisma.portfolioProject.findUnique({ where: { id: first!.id } });
expect(second?.publishedAt?.toISOString()).toBe(originalPublishedAt?.toISOString());
});
it("reports validation errors and creates nothing", async () => {
const category = await createCategory();
const url = await captureRedirect(() => saveProjectAction(projectForm(category.id, { titleEn: "" })));
expect(url).toContain("error=");
expect(await prisma.portfolioProject.count()).toBe(0);
});
it("reports a unique-constraint violation on duplicate slugs", async () => {
const category = await createCategory();
await createProject({ categoryId: category.id, slug: "case-study" });
const url = await captureRedirect(() => saveProjectAction(projectForm(category.id)));
expect(url).toContain("error=");
expect(decodeURIComponent(url)).toContain("eindeutig");
});
it("redirects unauthenticated callers to the admin root", async () => {
adminAuth.authenticated = false;
const url = await captureRedirect(() => saveProjectAction(projectForm("cat")));
expect(url).toBe("/");
});
});
describe("deleteProjectAction", () => {
it("deletes a project and its media usages", async () => {
const project = await createProject();
const url = await captureRedirect(() => deleteProjectAction(formDataFrom({ id: project.id })));
expect(url).toContain("success=");
expect(await prisma.portfolioProject.findUnique({ where: { id: project.id } })).toBeNull();
});
it("errors when the project does not exist", async () => {
const url = await captureRedirect(() => deleteProjectAction(formDataFrom({ id: "missing" })));
expect(url).toContain("error=");
});
it("redirects unauthenticated callers to the admin root", async () => {
adminAuth.authenticated = false;
const url = await captureRedirect(() => deleteProjectAction(formDataFrom({ id: "x" })));
expect(url).toBe("/");
});
});