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
This commit is contained in:
Moh
2026-08-06 02:27:20 +02:00
parent 0f48381894
commit e2e06be86e
50 changed files with 4975 additions and 26 deletions
+141
View File
@@ -0,0 +1,141 @@
import { describe, expect, it } from "vitest";
import {
getActivePortfolioCategories,
getActivePortfolioCategoryBySlug,
getAdminPortfolioCategories,
getAdminPortfolioProjectById,
getAdminPortfolioProjects,
getPublishedPortfolioProjectBySlug,
getPublishedPortfolioProjects,
} from "@/lib/portfolio";
import { prisma } from "@/lib/prisma";
import {
createAsset,
createCategory,
createMediaAsset,
createProject,
createSection,
} from "@/tests/helpers/factories";
describe("categories", () => {
it("lists admin categories with project counts, ordered", async () => {
const a = await createCategory({ slug: "a", sortOrder: 2 });
await createCategory({ slug: "b", sortOrder: 1 });
await createProject({ categoryId: a.id });
const categories = await getAdminPortfolioCategories();
expect(categories.map((c) => c.slug)).toEqual(["b", "a"]); // sortOrder asc
expect(categories.find((c) => c.slug === "a")?.projectCount).toBe(1);
expect(categories.find((c) => c.slug === "b")?.projectCount).toBe(0);
});
it("returns only active categories publicly", async () => {
await createCategory({ slug: "on", isActive: true });
await createCategory({ slug: "off", isActive: false });
const active = await getActivePortfolioCategories();
expect(active.map((c) => c.slug)).toEqual(["on"]);
});
it("finds an active category by slug and ignores inactive ones", async () => {
await createCategory({ slug: "visible", isActive: true });
await createCategory({ slug: "hidden", isActive: false });
expect((await getActivePortfolioCategoryBySlug("visible"))?.slug).toBe("visible");
expect(await getActivePortfolioCategoryBySlug("hidden")).toBeNull();
});
});
describe("admin projects", () => {
it("filters by status and category", async () => {
const cat = await createCategory();
await createProject({ categoryId: cat.id, slug: "pub", isPublished: true });
await createProject({ categoryId: cat.id, slug: "draft", isPublished: false });
const published = await getAdminPortfolioProjects({ status: "published" });
expect(published.map((p) => p.slug)).toEqual(["pub"]);
const drafts = await getAdminPortfolioProjects({ status: "draft" });
expect(drafts.map((p) => p.slug)).toEqual(["draft"]);
const byCategory = await getAdminPortfolioProjects({ categoryId: cat.id });
expect(byCategory.length).toBe(2);
});
it("maps localized content and nested sections/assets", async () => {
const project = await createProject({ slug: "mapped" });
await createSection(project.id, { titleEn: "Intro" });
await createAsset(project.id, { altEn: "Cover" });
const detail = await getAdminPortfolioProjectById(project.id);
expect(detail?.title.en).toBe("Title");
expect(detail?.sections[0].title.en).toBe("Intro");
expect(detail?.assets[0].alt.en).toBe("Cover");
});
it("attaches media bindings to a project fetched by id", async () => {
const project = await createProject();
const cover = await createMediaAsset();
await prisma.mediaUsage.create({
data: {
assetId: cover.id,
usageType: "PORTFOLIO_COVER",
entityType: "portfolio-project",
entityId: project.id,
fieldKey: "cover",
},
});
const detail = await getAdminPortfolioProjectById(project.id);
expect(detail?.coverMediaAssetId).toBe(cover.id);
});
it("returns null for a missing project id", async () => {
expect(await getAdminPortfolioProjectById("missing")).toBeNull();
});
});
describe("published projects", () => {
it("returns only published projects in active categories", async () => {
const activeCat = await createCategory({ isActive: true });
const inactiveCat = await createCategory({ isActive: false });
await createProject({ categoryId: activeCat.id, slug: "shown", isPublished: true });
await createProject({ categoryId: activeCat.id, slug: "hidden-draft", isPublished: false });
await createProject({ categoryId: inactiveCat.id, slug: "hidden-cat", isPublished: true });
const projects = await getPublishedPortfolioProjects();
expect(projects.map((p) => p.slug)).toEqual(["shown"]);
});
it("filters published projects by category slug", async () => {
const catA = await createCategory({ slug: "cat-a", isActive: true });
const catB = await createCategory({ slug: "cat-b", isActive: true });
await createProject({ categoryId: catA.id, slug: "in-a", isPublished: true });
await createProject({ categoryId: catB.id, slug: "in-b", isPublished: true });
const projects = await getPublishedPortfolioProjects({ categorySlug: "cat-a" });
expect(projects.map((p) => p.slug)).toEqual(["in-a"]);
});
it("finds a published project by slug and hides drafts", async () => {
await createProject({ slug: "live", isPublished: true });
await createProject({ slug: "wip", isPublished: false });
expect((await getPublishedPortfolioProjectBySlug("live"))?.slug).toBe("live");
expect(await getPublishedPortfolioProjectBySlug("wip")).toBeNull();
});
});
describe("referential integrity", () => {
it("restricts deleting a category that still has projects", async () => {
const cat = await createCategory();
await createProject({ categoryId: cat.id });
await expect(prisma.category.delete({ where: { id: cat.id } })).rejects.toThrow();
});
it("cascades section and asset deletion when a project is removed", async () => {
const project = await createProject();
await createSection(project.id);
await createAsset(project.id);
await prisma.portfolioProject.delete({ where: { id: project.id } });
expect(await prisma.portfolioSection.count()).toBe(0);
expect(await prisma.portfolioAsset.count()).toBe(0);
});
});