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:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user