ad8367c203
Search page: - Title and breadcrumb now read the station dictionaries and render the human-friendly route heading (e.g. 'Маршрут: Шереметьево - Пулково') for route/departure/arrival/flight search URLs, mirroring Angular. Details page: - Main H1 becomes 'Информация о рейсе: SU 6805, Москва - Санкт-Петербург' (carrier + flight number + origin/destination cities), not a bare flight number. - Add 'Детали рейса' section header above the accordion to match Angular's flight-details-wrapper layout. - Promote the airline block in BoardDetailsHeader: drop the legacy OperatorLogo copy with broken asset paths and hand off to the shared <OperatorLogo> under src/ui/flights. Render it with the 'авиакомпания' caption beside the enlarged flight number. - Replace hardcoded English 'Leg' / 'Total flying time' / 'Aircraft:' with i18n keys, added to all nine locale files. Test harness: - Add vi.mock for useDictionaries in the three suites that render OnlineBoardSearchPage (the new heading helper calls the hook and crashed without ApiClientProvider). 1256 tests passing.
193 lines
6.7 KiB
TypeScript
193 lines
6.7 KiB
TypeScript
/**
|
|
* Validates that the API layer can parse real responses captured from the
|
|
* test environment on 2026-04-17. Each test wires a real fixture into a
|
|
* mock fetch, then runs the production API function and asserts the
|
|
* parsed value has the structure the UI relies on.
|
|
*
|
|
* These tests catch drift between the declared TS types and the actual
|
|
* API contract. To refresh fixtures: `AEROFLOT_API_AUTH=user:pass
|
|
* ./scripts/fetch-api-fixtures.sh`.
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from "vitest";
|
|
import { ApiClient } from "@/shared/api/client";
|
|
import { searchFlights, getFlightDetails, getCalendarDays } from "@/features/online-board/api";
|
|
import { searchSchedule, getScheduleDetails, getScheduleCalendarDays } from "@/features/schedule/api";
|
|
import { searchDestinations, getFlightsMapCalendar } from "@/features/flights-map/api";
|
|
import { getPopularRequests } from "@/features/popular-requests/api";
|
|
import { fetchDictionaries } from "@/shared/dictionaries/api";
|
|
import { fixtures } from "../fixtures/api";
|
|
|
|
function clientWith(body: unknown, status = 200): { client: ApiClient; fetchMock: ReturnType<typeof vi.fn> } {
|
|
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
|
|
new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
const client = new ApiClient({
|
|
baseUrl: "https://api.test.com",
|
|
locale: "ru",
|
|
fetchImpl: fetchMock,
|
|
retry: { maxRetries: 0 },
|
|
});
|
|
return { client, fetchMock };
|
|
}
|
|
|
|
describe("online-board API against real fixtures", () => {
|
|
it("searchFlights parses a by-route response with multiple flights", async () => {
|
|
const { client } = clientWith(fixtures.boardByRoute);
|
|
const result = await searchFlights(client, {
|
|
dateFrom: "2026-04-17",
|
|
dateTo: "2026-04-18",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
});
|
|
expect(result.data.routes.length).toBeGreaterThan(0);
|
|
expect(result.data.partners).toBeInstanceOf(Array);
|
|
const first = result.data.routes[0]!;
|
|
expect(first.routeType).toMatch(/Direct|MultiLeg/);
|
|
expect(first.flightId.carrier).toBe("SU");
|
|
});
|
|
|
|
it("searchFlights parses a by-flight-number response", async () => {
|
|
const { client } = clientWith(fixtures.boardByFlight);
|
|
const result = await searchFlights(client, {
|
|
dateFrom: "2026-04-17",
|
|
dateTo: "2026-04-18",
|
|
flightNumber: "SU6951",
|
|
});
|
|
expect(result.data.routes[0]!.flightId.flightNumber).toBe("6951");
|
|
});
|
|
|
|
it("getFlightDetails parses the onlineboard/details response", async () => {
|
|
const { client } = clientWith(fixtures.onlineboardDetails);
|
|
const result = await getFlightDetails(client, {
|
|
flights: "SU6951",
|
|
dates: "2026-04-17",
|
|
});
|
|
expect(result.data.routes.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("getCalendarDays parses bitmask into date list", async () => {
|
|
const { client } = clientWith(fixtures.boardDaysFlight);
|
|
const days = await getCalendarDays(client, {
|
|
date: "2026-04-17",
|
|
searchType: "flight",
|
|
flightNumber: "SU6951",
|
|
});
|
|
expect(days.length).toBeGreaterThan(0);
|
|
expect(days[0]).toMatch(/^\d{8}$/);
|
|
});
|
|
});
|
|
|
|
describe("schedule API against real fixtures", () => {
|
|
it("searchSchedule parses an array response", async () => {
|
|
const { client } = clientWith(fixtures.scheduleSearch);
|
|
const result = await searchSchedule(client, {
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
dateFrom: "2026-04-18",
|
|
dateTo: "2026-04-22",
|
|
});
|
|
expect(Array.isArray(result)).toBe(true);
|
|
expect(result.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("getScheduleDetails parses a details response", async () => {
|
|
const { client } = clientWith(fixtures.scheduleDetails);
|
|
const result = await getScheduleDetails(client, {
|
|
flights: ["SU6951"],
|
|
dates: ["2026-04-17"],
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
});
|
|
expect(result.data).toBeDefined();
|
|
});
|
|
|
|
it("getScheduleCalendarDays parses the days response", async () => {
|
|
const { client } = clientWith(fixtures.scheduleDaysRoute);
|
|
const days = await getScheduleCalendarDays(client, {
|
|
date: "20260417",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
});
|
|
expect(days).toBeInstanceOf(Array);
|
|
});
|
|
});
|
|
|
|
describe("flights-map API against real fixtures", () => {
|
|
it("searchDestinations parses the destinations response", async () => {
|
|
const { client } = clientWith(fixtures.destinationsFrom);
|
|
const result = await searchDestinations(client, {
|
|
departure: "SVO",
|
|
dateFrom: "2026-04-18",
|
|
dateTo: "2026-04-22",
|
|
connections: 0,
|
|
});
|
|
expect(result.data.routes).toBeInstanceOf(Array);
|
|
expect(result.data.routes.length).toBeGreaterThan(0);
|
|
const first = result.data.routes[0]!;
|
|
expect(first.route).toBeInstanceOf(Array);
|
|
expect(typeof first.isDirect).toBe("boolean");
|
|
});
|
|
|
|
it("getFlightsMapCalendar parses the days bitmap", async () => {
|
|
const { client } = clientWith(fixtures.mapDaysRoute);
|
|
const days = await getFlightsMapCalendar(client, {
|
|
date: "20260417",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
});
|
|
expect(days).toBeInstanceOf(Array);
|
|
});
|
|
});
|
|
|
|
describe("popular-requests API against real fixture", () => {
|
|
it("getPopularRequests returns a list of discriminated requests", async () => {
|
|
const { client } = clientWith(fixtures.popularRequests);
|
|
const result = await getPopularRequests(client);
|
|
expect(result.length).toBeGreaterThan(0);
|
|
for (const req of result) {
|
|
expect(["FlightNumber", "Route", "RouteWithBack", "Departure", "Arrival"]).toContain(req.mode);
|
|
expect(["Schedule", "Onlineboard"]).toContain(req.type);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("dictionaries API against real fixtures", () => {
|
|
it("fetchDictionaries parses all four endpoints", async () => {
|
|
const calls = [
|
|
fixtures.dictionaryRegions,
|
|
fixtures.dictionaryCountries,
|
|
fixtures.dictionaryCities,
|
|
fixtures.dictionaryAirports,
|
|
];
|
|
let i = 0;
|
|
const fetchMock = vi.fn<typeof fetch>().mockImplementation(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify(calls[i++]!), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
),
|
|
);
|
|
const client = new ApiClient({
|
|
baseUrl: "https://api.test.com",
|
|
locale: "ru",
|
|
fetchImpl: fetchMock,
|
|
retry: { maxRetries: 0 },
|
|
});
|
|
|
|
const result = await fetchDictionaries(client);
|
|
expect(result.regions.length).toBeGreaterThan(0);
|
|
expect(result.countries.length).toBeGreaterThan(0);
|
|
expect(result.cities.length).toBeGreaterThan(0);
|
|
expect(result.airports.length).toBeGreaterThan(0);
|
|
expect(result.regions[0]!.title).toBeTypeOf("object");
|
|
expect(result.airports[0]!.code).toBeTypeOf("string");
|
|
});
|
|
});
|