aa61049229
Phase 4A: Define IFlightRoute, IMapMarker, IMapPolyline types; implement searchDestinations and getFlightsMapCalendar API functions with 11 tests; add useFlightsMapSearch, useFlightsMapCalendar hooks; add FEATURE_FLIGHTS_MAP env var for feature flag gating.
218 lines
6.1 KiB
TypeScript
218 lines
6.1 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { ApiClient } from "@/shared/api/client";
|
|
import { searchDestinations, getFlightsMapCalendar } from "./api";
|
|
import type {
|
|
FlightsMapSearchParams,
|
|
FlightsMapCalendarParams,
|
|
IDestinationsResponse,
|
|
IFlightsMapDaysResponse,
|
|
} from "./types";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function createMockClient(responseBody: unknown, status = 200) {
|
|
const mockFetch = vi.fn<typeof fetch>().mockResolvedValue(
|
|
new Response(JSON.stringify(responseBody), {
|
|
status,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
const client = new ApiClient({
|
|
baseUrl: "https://api.test.com",
|
|
locale: "ru",
|
|
fetchImpl: mockFetch,
|
|
retry: { maxRetries: 0 },
|
|
});
|
|
|
|
return { client, mockFetch };
|
|
}
|
|
|
|
function extractUrl(mockFetch: ReturnType<typeof vi.fn>): URL {
|
|
const call = mockFetch.mock.calls[0] as [string, ...unknown[]];
|
|
return new URL(call[0]);
|
|
}
|
|
|
|
const DESTINATIONS_RESPONSE: IDestinationsResponse = {
|
|
data: {
|
|
routes: [
|
|
{ route: ["SVO", "LED"], isDirect: true },
|
|
{ route: ["SVO", "DME", "LED"], isDirect: false },
|
|
],
|
|
},
|
|
};
|
|
|
|
const DAYS_RESPONSE: IFlightsMapDaysResponse = {
|
|
days: "01101",
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// searchDestinations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("searchDestinations", () => {
|
|
it("sends GET to destinations/1 with query params", async () => {
|
|
const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
|
|
|
|
const params: FlightsMapSearchParams = {
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
dateFrom: "20250601",
|
|
dateTo: "20251201",
|
|
};
|
|
|
|
await searchDestinations(client, params);
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.pathname).toBe("/destinations/1");
|
|
expect(url.searchParams.get("departure")).toBe("SVO");
|
|
expect(url.searchParams.get("arrival")).toBe("LED");
|
|
expect(url.searchParams.get("dateFrom")).toBe("20250601");
|
|
expect(url.searchParams.get("dateTo")).toBe("20251201");
|
|
expect(url.searchParams.get("connections")).toBe("0");
|
|
});
|
|
|
|
it("omits arrival param when not provided", async () => {
|
|
const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
|
|
|
|
await searchDestinations(client, {
|
|
departure: "SVO",
|
|
dateFrom: "20250601",
|
|
dateTo: "20251201",
|
|
});
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.searchParams.has("arrival")).toBe(false);
|
|
});
|
|
|
|
it("sends connections param when specified", async () => {
|
|
const { client, mockFetch } = createMockClient(DESTINATIONS_RESPONSE);
|
|
|
|
await searchDestinations(client, {
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
dateFrom: "20250601",
|
|
dateTo: "20251201",
|
|
connections: 1,
|
|
});
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.searchParams.get("connections")).toBe("1");
|
|
});
|
|
|
|
it("returns the deserialized response", async () => {
|
|
const { client } = createMockClient(DESTINATIONS_RESPONSE);
|
|
|
|
const result = await searchDestinations(client, {
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
dateFrom: "20250601",
|
|
dateTo: "20251201",
|
|
});
|
|
|
|
expect(result).toEqual(DESTINATIONS_RESPONSE);
|
|
});
|
|
|
|
it("throws on server error", async () => {
|
|
const { client } = createMockClient({ error: "internal" }, 500);
|
|
|
|
await expect(
|
|
searchDestinations(client, {
|
|
departure: "SVO",
|
|
dateFrom: "20250601",
|
|
dateTo: "20251201",
|
|
}),
|
|
).rejects.toThrow("HTTP 500");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// getFlightsMapCalendar
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("getFlightsMapCalendar", () => {
|
|
it("builds correct path for direct route", async () => {
|
|
const { client, mockFetch } = createMockClient(DAYS_RESPONSE);
|
|
|
|
await getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
});
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.pathname).toBe("/days/20250601/200/route/SVO-LED/flights-map/v1");
|
|
});
|
|
|
|
it("builds correct path for connecting route", async () => {
|
|
const { client, mockFetch } = createMockClient(DAYS_RESPONSE);
|
|
|
|
await getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: true,
|
|
});
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.pathname).toBe(
|
|
"/days/20250601/200/connections/SVO-LED-1/flights-map/v1",
|
|
);
|
|
});
|
|
|
|
it("builds correct path for departure-only (spider mode)", async () => {
|
|
const { client, mockFetch } = createMockClient(DAYS_RESPONSE);
|
|
|
|
await getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
connections: false,
|
|
});
|
|
|
|
const url = extractUrl(mockFetch);
|
|
expect(url.pathname).toBe("/days/20250601/200/departure/SVO/flights-map/v1");
|
|
});
|
|
|
|
it("parses binary days string into available date strings", async () => {
|
|
const { client } = createMockClient({ days: "10110" });
|
|
|
|
const result = await getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
});
|
|
|
|
expect(result).toEqual(["20250601", "20250603", "20250604"]);
|
|
});
|
|
|
|
it("returns empty array for empty days string", async () => {
|
|
const { client } = createMockClient({ days: "" });
|
|
|
|
const result = await getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
});
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("throws on server error", async () => {
|
|
const { client } = createMockClient({ error: "internal" }, 500);
|
|
|
|
await expect(
|
|
getFlightsMapCalendar(client, {
|
|
date: "20250601",
|
|
departure: "SVO",
|
|
arrival: "LED",
|
|
connections: false,
|
|
}),
|
|
).rejects.toThrow("HTTP 500");
|
|
});
|
|
});
|