Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

156 lines
4.6 KiB
TypeScript

import { afterEach, beforeEach, expect, it } from "vitest";
import { IdentifierError } from "../../../src/database/validate.js";
import {
loadContentMediaUsageFields,
MediaUsageFieldDiscoveryError,
} from "../../../src/media/usage/content-fields.js";
import { SchemaRegistry } from "../../../src/schema/registry.js";
import {
describeEachDialect,
setupForDialect,
teardownForDialect,
type DialectTestContext,
} from "../../utils/test-db.js";
describeEachDialect("content media usage field discovery", (dialect) => {
let ctx: DialectTestContext;
let registry: SchemaRegistry;
beforeEach(async () => {
ctx = await setupForDialect(dialect);
registry = new SchemaRegistry(ctx.db);
await registry.createCollection({ slug: "posts", label: "Posts" });
});
afterEach(async () => {
await teardownForDialect(ctx);
});
it("discovers V1 extraction fields and display fields separately", async () => {
await registry.createField("posts", { slug: "name", label: "Name", type: "string" });
await registry.createField("posts", { slug: "body", label: "Body", type: "portableText" });
await registry.createField("posts", { slug: "title", label: "Title", type: "string" });
await registry.createField("posts", { slug: "raw_data", label: "Raw Data", type: "json" });
await registry.createField("posts", { slug: "attachment", label: "Attachment", type: "file" });
await registry.createField("posts", { slug: "hero", label: "Hero", type: "image" });
await registry.createField("posts", {
slug: "sections",
label: "Sections",
type: "repeater",
validation: {
subFields: [
{ slug: "caption", type: "text", label: "Caption" },
{ slug: "image", type: "image", label: "Image" },
],
},
});
const discovery = await loadContentMediaUsageFields(ctx.db, "posts");
expect(discovery.displayFieldSlugs).toEqual(["title", "name"]);
expect(discovery.extractionFields).toEqual([
{ slug: "attachment", type: "file" },
{ slug: "body", type: "portableText" },
{ slug: "hero", type: "image" },
{
slug: "sections",
type: "repeater",
validation: { subFields: [{ slug: "image", type: "image" }] },
},
]);
});
it("filters unsupported repeater subfields and excludes repeaters without images", async () => {
await registry.createField("posts", {
slug: "sections",
label: "Sections",
type: "repeater",
validation: { subFields: [{ slug: "image", type: "image", label: "Image" }] },
});
await registry.createField("posts", {
slug: "downloads",
label: "Downloads",
type: "repeater",
validation: { subFields: [{ slug: "placeholder", type: "image", label: "Placeholder" }] },
});
await ctx.db
.updateTable("_emdash_fields")
.set({
validation: JSON.stringify({
subFields: [
{ slug: "download", type: "file", label: "Download" },
{ slug: "image", type: "image", label: "Image" },
{ slug: "caption", type: "text", label: "Caption" },
],
}),
})
.where("slug", "=", "sections")
.execute();
await ctx.db
.updateTable("_emdash_fields")
.set({
validation: JSON.stringify({
subFields: [{ slug: "download", type: "file", label: "Download" }],
}),
})
.where("slug", "=", "downloads")
.execute();
const discovery = await loadContentMediaUsageFields(ctx.db, "posts");
expect(discovery.extractionFields).toEqual([
{
slug: "sections",
type: "repeater",
validation: { subFields: [{ slug: "image", type: "image" }] },
},
]);
});
it("fails closed on malformed repeater validation", async () => {
await registry.createField("posts", {
slug: "sections",
label: "Sections",
type: "repeater",
validation: { subFields: [{ slug: "image", type: "image", label: "Image" }] },
});
await ctx.db
.updateTable("_emdash_fields")
.set({ validation: "{" })
.where("slug", "=", "sections")
.execute();
await expect(loadContentMediaUsageFields(ctx.db, "posts")).rejects.toThrow(
MediaUsageFieldDiscoveryError,
);
});
it("rejects supported fields with invalid slugs before they can become column refs", async () => {
const collection = await registry.getCollection("posts");
expect(collection).not.toBeNull();
await ctx.db
.insertInto("_emdash_fields")
.values({
id: "invalid-media-field",
collection_id: collection!.id,
slug: "bad-slug",
label: "Bad Slug",
type: "image",
column_type: "TEXT",
required: 0,
unique: 0,
default_value: null,
validation: null,
widget: null,
options: null,
sort_order: 0,
})
.execute();
await expect(loadContentMediaUsageFields(ctx.db, "posts")).rejects.toThrow(IdentifierError);
});
});