Add fetchDictionaries parallel-fetch layer

This commit is contained in:
2026-04-17 03:10:26 +03:00
parent da605f0576
commit dc373553d2
2 changed files with 92 additions and 0 deletions
+63
View File
@@ -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<string, unknown>,
): 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");
});
});
+29
View File
@@ -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<lang, string>` 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<IRawDictionaries> {
const [regions, countries, cities, airports] = await Promise.all([
client.get<IRawRegion[]>("dictionary/1/world_regions"),
client.get<IRawCountry[]>("dictionary/1/countries"),
client.get<IRawCity[]>("dictionary/1/cities"),
client.get<IRawAirport[]>("dictionary/1/airports"),
]);
return { regions, countries, cities, airports };
}