# test.build/core/validators/metadata.validator.test.ts

> 173 lines of code and 0 definitions.

Tree: Codebase Testing
Language: typescript
Canonical: https://banes-lab.com/anatomy/testing#file-testing-test-build-core-validators-metadata-validator-test-ts
Source text: https://banes-lab.com/source/testing/test.build/core/validators/metadata.validator.test.ts.txt

Listed in [test.build/core/validators](https://banes-lab.com/api/source/testing/test.build/core/validators.md), after [test.build/core/validators/link.validator.test.ts](https://banes-lab.com/source/testing/test.build/core/validators/link.validator.test.ts.md) and before [test.build/core/validators/payload.validator.test.ts](https://banes-lab.com/source/testing/test.build/core/validators/payload.validator.test.ts.md).

## Contained in

- [test.build/core/validators](https://banes-lab.com/anatomy/testing/folder-testing-test-build-core-validators.md)

## Source

```typescript
import { ADDRESS, EMPTY, PAGE } from "../converters/site.fixture.ts";
import {
    checkArticleImages,
    checkDates,
    checkEncodings,
    checkSchema,
} from "@banes-lab/build-scripts/core/validators/metadata.validator.ts";
import { describe, expect, it } from "vitest";
import {
    missingSchemaField,
    smallArticleImage,
    undatedWebPage,
    unknownSchemaProperty,
    unknownSchemaType,
    unreadableArticleImage,
} from "@banes-lab/build-scripts/configuration/strings/site.strings.ts";
import { readArtifact } from "@banes-lab/build-scripts/core/converters/build.converter.ts";

const TERMS_FILE = "terms.html";
const EMPTY_FILE = "empty.html";

const isServed = function isServed(url: string): boolean {
    return url.endsWith("/json/terms");
};

const htmlOf = function htmlOf(graph: Record<string, unknown>): string {
    return `<!doctype html><html><head><script type="application/ld+json">${JSON.stringify(graph)}</script></head><body></body></html>`;
};

const schemaPageOf = function schemaPageOf(entity: Record<string, unknown>): string {
    return htmlOf({ "@graph": [{ "@type": "Organization" }, { "@type": "WebPage", url: ADDRESS }, entity] });
};

describe("checkSchema", () => {
    it("wants an organization and a web page entity for the route's address in the structured data", () => {
        const artifact = readArtifact(PAGE);
        expect(checkSchema(TERMS_FILE, artifact, ADDRESS)).toStrictEqual([]);
        expect(checkSchema(TERMS_FILE, artifact, "https://example.test/other")).toHaveLength(1);
        expect(checkSchema(EMPTY_FILE, readArtifact(EMPTY), ADDRESS)).toHaveLength(2);
    });

    it("reports each field a declared schema type expects that its entity leaves out", () => {
        const dataset = { "@type": "Dataset", description: "d", license: "l", name: "n" };
        const lacking = readArtifact(schemaPageOf(dataset));
        expect(checkSchema(TERMS_FILE, lacking, ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: missingSchemaField("Dataset", "creator") },
        ]);
        const whole = readArtifact(schemaPageOf({ ...dataset, creator: "c" }));
        expect(checkSchema(TERMS_FILE, whole, ADDRESS)).toStrictEqual([]);
    });

    it("reports a property its type does not register and a type with no registered properties, nested ones included", () => {
        const html = htmlOf({
            "@graph": [
                { "@type": "Organization" },
                { "@type": "WebPage", url: ADDRESS },
                { "@type": "CreativeWork", codeRepository: "r", creator: { "@type": "Robot", name: "n" }, name: "n" },
            ],
        });
        expect(checkSchema(TERMS_FILE, readArtifact(html), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: unknownSchemaProperty("CreativeWork", "codeRepository") },
            { file: TERMS_FILE, message: unknownSchemaType("Robot") },
        ]);
    });

    it("holds a nested address and a nested article to their declared fields", () => {
        const street = { "@type": "PostalAddress", addressCountry: "BE", addressLocality: "Ostend" };
        const organization = { "@type": "Organization", address: street };
        const article = { "@type": "TechArticle", author: "a", headline: "h", image: "i" };
        const lacking = htmlOf({ "@graph": [organization, { "@type": "WebPage", mainEntity: article, url: ADDRESS }] });
        expect(checkSchema(TERMS_FILE, readArtifact(lacking), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: missingSchemaField("PostalAddress", "postalCode") },
            { file: TERMS_FILE, message: missingSchemaField("PostalAddress", "streetAddress") },
            { file: TERMS_FILE, message: missingSchemaField("TechArticle", "datePublished") },
            { file: TERMS_FILE, message: missingSchemaField("TechArticle", "dateModified") },
        ]);
        const whole = htmlOf({
            "@graph": [
                { ...organization, address: { ...street, postalCode: "8400", streetAddress: "s" } },
                {
                    "@type": "WebPage",
                    mainEntity: { ...article, dateModified: "d", datePublished: "d" },
                    url: ADDRESS,
                },
            ],
        });
        expect(checkSchema(TERMS_FILE, readArtifact(whole), ADDRESS)).toStrictEqual([]);
    });

    it("takes a profile page as the route's web page", () => {
        const html = htmlOf({ "@graph": [{ "@type": "Organization" }, { "@type": "ProfilePage", url: ADDRESS }] });
        expect(checkSchema(TERMS_FILE, readArtifact(html), ADDRESS)).toStrictEqual([]);
    });

    it("wants a tech article to carry its headline, image, author and both dates", () => {
        const article = { "@type": "TechArticle", author: "a", headline: "h", image: "i" };
        expect(checkSchema(TERMS_FILE, readArtifact(schemaPageOf(article)), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: missingSchemaField("TechArticle", "datePublished") },
            { file: TERMS_FILE, message: missingSchemaField("TechArticle", "dateModified") },
        ]);
        const dated = {
            ...article,
            dateModified: "2026-10-07T00:00:00+00:00",
            datePublished: "2026-09-30T00:00:00+00:00",
        };
        expect(checkSchema(TERMS_FILE, readArtifact(schemaPageOf(dated)), ADDRESS)).toStrictEqual([]);
    });
});

describe("checkDates", () => {
    const pageWith = function pageWith(dates: Record<string, unknown>): string {
        return htmlOf({ "@graph": [{ "@type": "WebPage", url: ADDRESS, ...dates }] });
    };

    it("accepts a route's web page dated with two ISO dates and times that carry their offset", () => {
        const dated = pageWith({ dateModified: "2026-10-07T00:00:00+00:00", datePublished: "2026-09-30T08:15:00Z" });
        expect(checkDates(TERMS_FILE, readArtifact(dated), ADDRESS)).toStrictEqual([]);
    });

    it("reports a missing date, an impossible date and a date without a time zone", () => {
        const page = pageWith({ dateModified: "2026-13-40T00:00:00+00:00", datePublished: "2026-09-30" });
        expect(checkDates(TERMS_FILE, readArtifact(page), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: undatedWebPage("datePublished", "2026-09-30") },
            { file: TERMS_FILE, message: undatedWebPage("dateModified", "2026-13-40T00:00:00+00:00") },
        ]);
        expect(checkDates(TERMS_FILE, readArtifact(pageWith({})), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: undatedWebPage("datePublished", "undefined") },
            { file: TERMS_FILE, message: undatedWebPage("dateModified", "undefined") },
        ]);
    });

    it("holds a dated article inside the page to the same date shape", () => {
        const article = { "@type": "TechArticle", dateModified: "2026-10-07T00:00:00+00:00", datePublished: "2026-09-30" };
        const page = pageWith({
            dateModified: "2026-10-07T00:00:00+00:00",
            datePublished: "2026-09-30T00:00:00+00:00",
            mainEntity: article,
        });
        expect(checkDates(TERMS_FILE, readArtifact(page), ADDRESS)).toStrictEqual([
            { file: TERMS_FILE, message: undatedWebPage("datePublished", "2026-09-30") },
        ]);
    });

    it("leaves a page with no web page entity for the address to the schema check", () => {
        expect(checkDates(EMPTY_FILE, readArtifact(EMPTY), ADDRESS)).toStrictEqual([]);
    });
});

describe("checkArticleImages", () => {
    const articleWith = function articleWith(image: unknown): string {
        return htmlOf({ "@graph": [{ "@type": "WebPage", mainEntity: { "@type": "TechArticle", image }, url: ADDRESS }] });
    };
    const pixels = new Map([
        ["https://example.test/wide.png", 810_000],
        ["https://example.test/tiny.png", 40_000],
    ]);
    const pixelsOf = (url: string): number | null => pixels.get(url) ?? null;

    it("accepts an article whose every image the build holds at 50,000 pixels or more", () => {
        expect(checkArticleImages(TERMS_FILE, readArtifact(articleWith(["https://example.test/wide.png"])), pixelsOf)).toStrictEqual([]);
    });

    it("reports an image the build does not hold and an image under the minimum", () => {
        const html = articleWith(["https://example.test/gone.png", "https://example.test/tiny.png"]);
        expect(checkArticleImages(TERMS_FILE, readArtifact(html), pixelsOf)).toStrictEqual([
            { file: TERMS_FILE, message: unreadableArticleImage("https://example.test/gone.png") },
            { file: TERMS_FILE, message: smallArticleImage("https://example.test/tiny.png", 40_000, 50_000) },
        ]);
        expect(checkArticleImages(TERMS_FILE, readArtifact(articleWith("https://example.test/tiny.png")), pixelsOf)).toHaveLength(1);
    });
});

describe("checkEncodings", () => {
    it("reports every encoding the structured data names that the build does not serve", () => {
        const html = htmlOf({
            "@graph": [
                {
                    "@type": "WebPage",
                    encoding: [{ contentUrl: "https://example.test/json/terms" }],
                    hasPart: [{ encoding: [{ contentUrl: "https://example.test/json/terms/gone" }] }],
                },
            ],
        });
        expect(checkEncodings(TERMS_FILE, readArtifact(html), isServed)).toStrictEqual([
            {
                file: TERMS_FILE,
                message:
                    "The structured data names https://example.test/json/terms/gone as an encoding of the page, and the build does not serve it.",
            },
        ]);
    });
});
```
