diff --git a/src/shared/dictionaries/api.test.ts b/src/shared/dictionaries/api.test.ts new file mode 100644 index 00000000..0de5db52 --- /dev/null +++ b/src/shared/dictionaries/api.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi } from "vitest"; +import { fetchDictionaries } from "./api.js"; +import type { ApiClient } from "@/shared/api/client.js"; + +function makeClient( + responses: Record, +): ApiClient { + return { + get: vi.fn(async (path: string) => { + const match = Object.keys(responses).find((k) => path.includes(k)); + if (!match) throw new Error(`Unexpected path: ${path}`); + return responses[match]; + }), + } as unknown as ApiClient; +} + +describe("fetchDictionaries", () => { + it("fetches four endpoints in parallel and returns a combined shape", async () => { + const client = makeClient({ + world_regions: [{ world_region_id: 1, title: { ru: "Европа" } }], + countries: [{ code: "RU", title: { ru: "Россия" }, world_region_id: 1 }], + cities: [ + { code: "MOW", title: { ru: "Москва" }, country_code: "RU" }, + ], + airports: [ + { code: "SVO", city_code: "MOW", title: { ru: "Шереметьево" }, has_afl_flights: true }, + ], + }); + + const result = await fetchDictionaries(client); + + expect(result.regions).toHaveLength(1); + expect(result.countries).toHaveLength(1); + expect(result.cities).toHaveLength(1); + expect(result.airports).toHaveLength(1); + expect(result.cities[0]!.code).toBe("MOW"); + }); + + it("calls exactly the four dictionary paths", async () => { + const getMock = vi.fn(async (_path: string) => []); + const client = { get: getMock } as unknown as ApiClient; + + await fetchDictionaries(client); + + const calledPaths = getMock.mock.calls.map((c) => c[0] as string); + expect(calledPaths).toContain("dictionary/1/world_regions"); + expect(calledPaths).toContain("dictionary/1/countries"); + expect(calledPaths).toContain("dictionary/1/cities"); + expect(calledPaths).toContain("dictionary/1/airports"); + expect(getMock).toHaveBeenCalledTimes(4); + }); + + it("propagates a rejection from any endpoint", async () => { + const client = { + get: vi.fn(async (path: string) => { + if (path.includes("airports")) throw new Error("boom"); + return []; + }), + } as unknown as ApiClient; + + await expect(fetchDictionaries(client)).rejects.toThrow("boom"); + }); +}); diff --git a/src/shared/dictionaries/api.ts b/src/shared/dictionaries/api.ts new file mode 100644 index 00000000..970ecb36 --- /dev/null +++ b/src/shared/dictionaries/api.ts @@ -0,0 +1,29 @@ +/** + * Parallel fetch of the four dictionary endpoints. + * + * Paths match the Angular `NetworkService.getDictionary` pattern: + * `/dictionary/1/{name}`. Locale is not part of the path; the server + * returns all titles as a `Record` and the consumer picks. + */ + +import type { ApiClient } from "@/shared/api/client.js"; +import type { + IRawDictionaries, + IRawRegion, + IRawCountry, + IRawCity, + IRawAirport, +} from "./types.js"; + +export async function fetchDictionaries( + client: ApiClient, +): Promise { + const [regions, countries, cities, airports] = await Promise.all([ + client.get("dictionary/1/world_regions"), + client.get("dictionary/1/countries"), + client.get("dictionary/1/cities"), + client.get("dictionary/1/airports"), + ]); + + return { regions, countries, cities, airports }; +}