import { describe, expect, test } from "vitest"; import { createPublishedProjectBundleFixture } from "@webstudio-is/protocol/fixtures"; import { createStructuredAssetQueryResourceBody } from "@webstudio-is/sdk"; import { compileContentArtifact, createAssetIndex, createCanonicalAssetFileEntry, } from "@webstudio-is/content-engine/compiler"; import { getContentDatabasePublishDiagnostics, getMdxTemplatePublishDiagnostics, } from "./content-database.server"; describe("content database publish diagnostics", () => { test("reports the file and template omitted from a published page", async () => { const bundle = createPublishedProjectBundleFixture(); const rootId = bundle.build.pages.pages[0].rootInstanceId; bundle.build.instances = [ [ rootId, { id: rootId, type: "instance", component: "ws:block", children: [{ type: "id", value: "templates" }], }, ], [ "templates", { id: "templates", type: "instance", component: "ws:block-template", children: [], }, ], ]; bundle.build.props = [ [ "source", { id: "source", instanceId: rootId, name: "src", type: "asset", value: "article", }, ], ]; bundle.assets = [ { id: "article", projectId: bundle.build.projectId, name: "revision.mdx", filename: "post", type: "file", format: "mdx", size: 11, meta: {}, createdAt: "2026-09-08T00:00:00Z", }, ]; bundle.assetIndex = { ...(await createAssetIndex({ projectId: bundle.build.projectId, entries: [ createCanonicalAssetFileEntry({ projectId: bundle.build.projectId, document: { _id: "article", _type: "asset.file", name: "post.mdx", path: "post.mdx", key: "post", extension: "mdx", mimeType: "text/mdx", size: 11, revision: "revision", contentRef: "article.mdx", properties: {}, }, }), ], maxBytes: 5000, })), contents: { "article.mdx": "" }, }; expect(await getMdxTemplatePublishDiagnostics(bundle)).toEqual([ { assetId: "article", filename: "post.mdx", blockInstanceId: rootId, templateName: "Missing", }, ]); bundle.build.instances.push([ "missing", { id: "missing", type: "instance", name: "Missing", component: "Box", children: [], }, ]); bundle.build.instances[1][1].children = [{ type: "id", value: "missing" }]; expect(await getMdxTemplatePublishDiagnostics(bundle)).toEqual([]); }); test("classifies affected dynamic and static resources individually", async () => { const resourceId = "blog-posts"; const overviewResourceId = "blog-overview"; const createEntry = (id: string, bytes: number) => createCanonicalAssetFileEntry({ projectId: "source-project", document: { _id: id, _type: "asset.file", name: `${id}.md`, path: `blog/${id}.md`, key: id, extension: "md", mimeType: "text/markdown", size: bytes, revision: `revision-${id}`, contentRef: `files/${id}.md`, properties: { value: "x".repeat(bytes) }, }, }); const assetIndex = await createAssetIndex({ projectId: "source-project", entries: [createEntry("small", 100), createEntry("large", 10_000)], maxBytes: 5_000, }); const bundle = createPublishedProjectBundleFixture({ build: { props: [ [ "posts-prop", { id: "posts-prop", instanceId: "collection", name: "data", type: "resource", value: resourceId, }, ], [ "overview-prop", { id: "overview-prop", instanceId: "collection", name: "overview", type: "resource", value: overviewResourceId, }, ], ], resources: [ [ resourceId, { id: resourceId, name: "Blog posts", control: "system", method: "post", url: '"/$resources/assets"', headers: [], body: createStructuredAssetQueryResourceBody({ where: { all: [] }, sort: [], limit: "pageSize", offset: "0", output: { mode: "base", includeMetadata: true }, content: { mode: "none" }, }), }, ], [ overviewResourceId, { id: overviewResourceId, name: "Blog overview", control: "system", method: "post", url: '"/$resources/assets"', headers: [], body: createStructuredAssetQueryResourceBody({ where: { all: [] }, sort: [], limit: "10", offset: "0", output: { mode: "base", includeMetadata: true }, content: { mode: "none" }, }), }, ], ], }, assetIndex, }); expect(getContentDatabasePublishDiagnostics(bundle)).toMatchObject({ stats: { includedDocumentCount: 1, omittedDocumentCount: 1, maxBytes: 5_000, omissionReason: "size", truncated: true, }, affectedResources: [ { name: "Blog posts", kind: "dynamic" }, { name: "Blog overview", kind: "static" }, ], }); }); test("does nothing when the build has no content query", () => { expect( getContentDatabasePublishDiagnostics( createPublishedProjectBundleFixture() ) ).toBeUndefined(); }); test("reports unavailable content for affected static resources", async () => { const resourceId = "blog-overview"; const { artifact: assetIndex } = await compileContentArtifact({ projectId: "source-project", entries: [ { ...createCanonicalAssetFileEntry({ projectId: "source-project", document: { _id: "post", _type: "asset.file", name: "post.md", path: "blog/post.md", key: "post", extension: "md", mimeType: "text/markdown", size: 100, revision: "revision-post", contentRef: "files/post.md", properties: {}, }, }), contentRequired: true, }, ], maxBytes: 5_000, }); const bundle = createPublishedProjectBundleFixture({ build: { props: [ [ "overview-prop", { id: "overview-prop", instanceId: "collection", name: "data", type: "resource", value: resourceId, }, ], ], resources: [ [ resourceId, { id: resourceId, name: "Blog overview", control: "system", method: "post", url: '"/$resources/assets"', headers: [], body: createStructuredAssetQueryResourceBody({ where: { all: [] }, sort: [], limit: "10", offset: "0", output: { mode: "base", includeMetadata: true }, content: { mode: "none" }, }), }, ], ], }, assetIndex, }); expect(getContentDatabasePublishDiagnostics(bundle)).toMatchObject({ stats: { omissionReason: "unavailable", truncated: true }, affectedResources: [{ name: "Blog overview", kind: "static" }], }); }); });