test.build/core/validators/metadata.validator.test.ts
test.build/core/validators/metadata.validator.test.ts is a file in Codebase Testing. 173 lines of code and 0 definitions.
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.",
},
]);
});
});