Audit OpenGraph + canonical + hreflang per TZ 4.1.19/20 (assertion tests)

All three feature seo builders already emitted the full OG set (title,
description, url, image, type, locale, site_name), canonical with no
query params, and hreflang for all 9 locales + x-default. No builder
gaps found. Added explicit §4.1.19/20 requirement-ID test cases to each
seo.test.ts so the contractual coverage is machine-verifiable.
This commit is contained in:
2026-04-22 01:48:00 +03:00
parent 944015d658
commit 5286049420
3 changed files with 320 additions and 0 deletions
+71
View File
@@ -104,3 +104,74 @@ describe("buildFlightsMapJsonLd", () => {
expect(result.name).toContain("Flight Map");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.19 — OpenGraph completeness assertions (Flights Map)
// ---------------------------------------------------------------------------
describe("4.1.19-R: OpenGraph completeness (Flights Map)", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.19-R: Flights Map page has all required OG tags", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.20 — Robots, canonical, hreflang assertions (Flights Map)
// ---------------------------------------------------------------------------
describe("4.1.20-R: canonical + hreflang (Flights Map)", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.20-R: Flights Map canonical has no query params", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
expect(seo.canonical).not.toContain("?");
expect(seo.canonical).toBe(`${ORIGIN}/ru/flights-map`);
});
it("4.1.20-R: hreflang has all 9 languages + x-default", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
expect(seo.hreflang.length).toBeGreaterThanOrEqual(9);
expect(seo.hreflang.some((h) => h.lang === "x-default")).toBe(true);
});
it("4.1.20-R: hreflang covers all 9 supported languages", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
const langs = seo.hreflang.map((h) => h.lang);
expect(langs).toContain("ru");
expect(langs).toContain("en");
expect(langs).toContain("es");
expect(langs).toContain("fr");
expect(langs).toContain("it");
expect(langs).toContain("ja");
expect(langs).toContain("ko");
expect(langs).toContain("zh");
expect(langs).toContain("de");
});
it("4.1.20-R: hreflang hrefs are absolute URLs", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
for (const entry of seo.hreflang) {
expect(entry.href).toMatch(/^https:\/\//);
}
});
it("4.1.20-R: noindex is not set on the Flights Map page (indexable)", () => {
const seo = buildFlightsMapSeo(stubT, "ru", ORIGIN);
expect(seo.noindex).toBeFalsy();
});
});
+149
View File
@@ -471,3 +471,152 @@ describe("buildFlightDetailsSeo", () => {
expect(result.og.type).toBe("article");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.19 — OpenGraph completeness assertions (all required tags present)
// ---------------------------------------------------------------------------
describe("4.1.19-R: OpenGraph completeness", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.19-R: Online-Board start page has all required OG tags", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Online-Board flight search has all required OG tags", () => {
const params = { type: "flight" as const, carrier: "SU", flightNumber: "0100", date: "20250115" };
const seo = buildFlightSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Online-Board departure search has all required OG tags", () => {
const params = { type: "departure" as const, station: "SVO", date: "20250115" };
const seo = buildDepartureSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Online-Board arrival search has all required OG tags", () => {
const params = { type: "arrival" as const, station: "JFK", date: "20250115" };
const seo = buildArrivalSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Online-Board route search has all required OG tags", () => {
const params = { type: "route" as const, departure: "SVO", arrival: "JFK", date: "20250115" };
const seo = buildRouteSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Online-Board flight details has all required OG tags", () => {
const seo = buildFlightDetailsSeoFromId(
stubT,
{ carrier: "SU", flightNumber: "0100", date: "20250115" },
"ru",
ORIGIN,
);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("article");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.20 — Robots, canonical, hreflang assertions
// ---------------------------------------------------------------------------
describe("4.1.20-R: canonical + hreflang", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.20-R: canonical URL is normalized (no query params)", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
expect(seo.canonical).not.toContain("?");
expect(seo.canonical).toBe(`${ORIGIN}/ru/onlineboard`);
});
it("4.1.20-R: hreflang has all 9 languages + x-default", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
expect(seo.hreflang.length).toBeGreaterThanOrEqual(9);
expect(seo.hreflang.some((h) => h.lang === "x-default")).toBe(true);
});
it("4.1.20-R: hreflang covers all 9 supported languages", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
const langs = seo.hreflang.map((h) => h.lang);
expect(langs).toContain("ru");
expect(langs).toContain("en");
expect(langs).toContain("es");
expect(langs).toContain("fr");
expect(langs).toContain("it");
expect(langs).toContain("ja");
expect(langs).toContain("ko");
expect(langs).toContain("zh");
expect(langs).toContain("de");
});
it("4.1.20-R: hreflang hrefs are absolute URLs", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
for (const entry of seo.hreflang) {
expect(entry.href).toMatch(/^https:\/\//);
}
});
it("4.1.20-R: flight search canonical has no query params", () => {
const params = { type: "flight" as const, carrier: "SU", flightNumber: "0100", date: "20250115" };
const seo = buildFlightSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.canonical).not.toContain("?");
expect(seo.canonical).toMatch(/^https:\/\//);
});
it("4.1.20-R: noindex is not set on standard indexable pages (defaults to index,follow)", () => {
const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
// Standard pages must be indexable — noindex prop must be absent/falsy
expect(seo.noindex).toBeFalsy();
});
});
+100
View File
@@ -367,3 +367,103 @@ describe("buildScheduleDetailsSeo", () => {
expect(result.og.type).toBe("article");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.19 — OpenGraph completeness assertions
// ---------------------------------------------------------------------------
describe("4.1.19-R: OpenGraph completeness (Schedule)", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.19-R: Schedule start page has all required OG tags", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Schedule search page has all required OG tags", () => {
const params = {
type: "route" as const,
outbound: { departure: "MOW", arrival: "LED", dateFrom: "20250115", dateTo: "20250120" },
};
const seo = buildScheduleSearchSeo(stubT, params, "ru", ORIGIN);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("website");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
it("4.1.19-R: Schedule details page has all required OG tags", () => {
const fIds: IScheduleFlightId[] = [{ carrier: "SU", flightNumber: "0012", date: "20220527" }];
const seo = buildScheduleDetailsSeo(stubT, [], "ru", ORIGIN, fIds);
expect(seo.og.title).toBeTruthy();
expect(seo.og.description).toBeTruthy();
expect(seo.og.url).toMatch(/^https:\/\//);
expect(seo.og.image).toMatch(/^https:\/\//);
expect(seo.og.type).toBe("article");
expect(seo.og.locale).toBe("ru");
expect(seo.og.siteName).toBe("Aeroflot");
});
});
// ---------------------------------------------------------------------------
// TZ §4.1.20 — Robots, canonical, hreflang assertions (Schedule)
// ---------------------------------------------------------------------------
describe("4.1.20-R: canonical + hreflang (Schedule)", () => {
const ORIGIN = "https://flights.aeroflot.ru";
it("4.1.20-R: Schedule start canonical has no query params", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
expect(seo.canonical).not.toContain("?");
expect(seo.canonical).toBe(`${ORIGIN}/ru/schedule`);
});
it("4.1.20-R: hreflang has all 9 languages + x-default", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
expect(seo.hreflang.length).toBeGreaterThanOrEqual(9);
expect(seo.hreflang.some((h) => h.lang === "x-default")).toBe(true);
});
it("4.1.20-R: hreflang covers all 9 supported languages", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
const langs = seo.hreflang.map((h) => h.lang);
expect(langs).toContain("ru");
expect(langs).toContain("en");
expect(langs).toContain("es");
expect(langs).toContain("fr");
expect(langs).toContain("it");
expect(langs).toContain("ja");
expect(langs).toContain("ko");
expect(langs).toContain("zh");
expect(langs).toContain("de");
});
it("4.1.20-R: hreflang hrefs are absolute URLs", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
for (const entry of seo.hreflang) {
expect(entry.href).toMatch(/^https:\/\//);
}
});
it("4.1.20-R: noindex is not set on standard indexable Schedule pages", () => {
const seo = buildScheduleStartSeo(stubT, "ru", ORIGIN);
expect(seo.noindex).toBeFalsy();
});
});