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:
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
countMediaUsageReferences,
|
||||
createMediaAsset,
|
||||
deleteEntityMediaUsages,
|
||||
getAdminMediaAssets,
|
||||
getMediaAssetById,
|
||||
getMediaOptions,
|
||||
getPortfolioMediaBindings,
|
||||
replaceEntityMediaUsages,
|
||||
} from "@/lib/media";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { createMediaAsset as seedAsset } from "@/tests/helpers/factories";
|
||||
|
||||
describe("createMediaAsset / getMediaAssetById", () => {
|
||||
it("creates and reads back an asset with usages", async () => {
|
||||
const created = await createMediaAsset({
|
||||
source: "EXTERNAL",
|
||||
kind: "IMAGE",
|
||||
url: "https://cdn/x.png",
|
||||
fileName: "x.png",
|
||||
label: "X",
|
||||
});
|
||||
const found = await getMediaAssetById(created.id);
|
||||
expect(found?.url).toBe("https://cdn/x.png");
|
||||
expect(found?.usages).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns null for a missing asset", async () => {
|
||||
expect(await getMediaAssetById("nope")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMediaOptions", () => {
|
||||
it("filters by kind", async () => {
|
||||
await seedAsset({ kind: "IMAGE" });
|
||||
await seedAsset({ kind: "DOCUMENT" });
|
||||
const images = await getMediaOptions({ kind: "IMAGE" });
|
||||
expect(images.every((a) => a.kind === "IMAGE")).toBe(true);
|
||||
const all = await getMediaOptions();
|
||||
expect(all.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAdminMediaAssets", () => {
|
||||
it("returns newest first with usage details", async () => {
|
||||
const a = await seedAsset();
|
||||
await createMediaUsageFor(a.id);
|
||||
const list = await getAdminMediaAssets();
|
||||
expect(list.length).toBe(1);
|
||||
expect(list[0].usages.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replaceEntityMediaUsages", () => {
|
||||
it("replaces existing usages transactionally", async () => {
|
||||
const a1 = await seedAsset();
|
||||
const a2 = await seedAsset();
|
||||
|
||||
await replaceEntityMediaUsages({
|
||||
entityType: "portfolio-project",
|
||||
entityId: "p1",
|
||||
usages: [{ assetId: a1.id, usageType: "PORTFOLIO_COVER", fieldKey: "cover" }],
|
||||
});
|
||||
expect(await countMediaUsageReferences(a1.id)).toBe(1);
|
||||
|
||||
await replaceEntityMediaUsages({
|
||||
entityType: "portfolio-project",
|
||||
entityId: "p1",
|
||||
usages: [{ assetId: a2.id, usageType: "PORTFOLIO_COVER", fieldKey: "cover" }],
|
||||
});
|
||||
expect(await countMediaUsageReferences(a1.id)).toBe(0);
|
||||
expect(await countMediaUsageReferences(a2.id)).toBe(1);
|
||||
});
|
||||
|
||||
it("clears usages when given an empty list", async () => {
|
||||
const a1 = await seedAsset();
|
||||
await replaceEntityMediaUsages({
|
||||
entityType: "portfolio-project",
|
||||
entityId: "p2",
|
||||
usages: [{ assetId: a1.id, usageType: "PORTFOLIO_ASSET", fieldKey: "a" }],
|
||||
});
|
||||
await replaceEntityMediaUsages({ entityType: "portfolio-project", entityId: "p2", usages: [] });
|
||||
expect(await countMediaUsageReferences(a1.id)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteEntityMediaUsages", () => {
|
||||
it("removes only the target entity's usages", async () => {
|
||||
const a1 = await seedAsset();
|
||||
await replaceEntityMediaUsages({
|
||||
entityType: "portfolio-project",
|
||||
entityId: "keep",
|
||||
usages: [{ assetId: a1.id, usageType: "PORTFOLIO_ASSET", fieldKey: "a" }],
|
||||
});
|
||||
await replaceEntityMediaUsages({
|
||||
entityType: "portfolio-project",
|
||||
entityId: "drop",
|
||||
usages: [{ assetId: a1.id, usageType: "PORTFOLIO_ASSET", fieldKey: "b" }],
|
||||
});
|
||||
await deleteEntityMediaUsages("portfolio-project", "drop");
|
||||
expect(await countMediaUsageReferences(a1.id)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPortfolioMediaBindings", () => {
|
||||
it("routes usages into cover / section / asset buckets", async () => {
|
||||
const cover = await seedAsset();
|
||||
const section = await seedAsset();
|
||||
const asset = await seedAsset();
|
||||
|
||||
await prisma.mediaUsage.createMany({
|
||||
data: [
|
||||
{ assetId: cover.id, usageType: "PORTFOLIO_COVER", entityType: "portfolio-project", entityId: "proj", fieldKey: "cover" },
|
||||
{ assetId: section.id, usageType: "PORTFOLIO_SECTION", entityType: "portfolio-project", entityId: "proj", fieldKey: "sec_1" },
|
||||
{ assetId: asset.id, usageType: "PORTFOLIO_ASSET", entityType: "portfolio-project", entityId: "proj", fieldKey: "ast_1" },
|
||||
],
|
||||
});
|
||||
|
||||
const bindings = await getPortfolioMediaBindings("proj");
|
||||
expect(bindings.coverAssetId).toBe(cover.id);
|
||||
expect(bindings.sectionAssetIds.sec_1).toBe(section.id);
|
||||
expect(bindings.assetIds.ast_1).toBe(asset.id);
|
||||
});
|
||||
|
||||
it("returns empty bindings for an unknown project", async () => {
|
||||
const bindings = await getPortfolioMediaBindings("missing");
|
||||
expect(bindings).toEqual({ coverAssetId: null, sectionAssetIds: {}, assetIds: {} });
|
||||
});
|
||||
});
|
||||
|
||||
async function createMediaUsageFor(assetId: string) {
|
||||
await prisma.mediaUsage.create({
|
||||
data: { assetId, usageType: "GENERIC", entityType: "e", entityId: "1", fieldKey: "f" },
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user