New /api/admin/ares proxy (browser cannot reach ares.gov.cz - CORS+CSP): GET /ico/:ico and GET /search?q= map the MFCR REST API to the form shape (street incl. orientation numbers, "511 01" PSC format, DIC). Guarded by customers.create/edit + warehouse.manage; token errors map to Czech messages. Shared AresAdornment button sits in the Nazev and ICO fields of both modals: ICO mode fills directly, name mode searches with a result picker. Tested against live ARES (BOHA, ICO 22599851). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
import { aresLookupByIco, aresSearchByName } from "../services/ares.service";
|
|
|
|
// ARES is a true external — mock global fetch (the only allowed mock class).
|
|
const SUBJECT = {
|
|
ico: "22599851",
|
|
obchodniJmeno: "BOHA Automation s.r.o.",
|
|
dic: "CZ22599851",
|
|
sidlo: {
|
|
nazevStatu: "Česká republika",
|
|
nazevObce: "Turnov",
|
|
nazevCastiObce: "Turnov",
|
|
nazevUlice: "Nádražní",
|
|
cisloDomovni: 485,
|
|
psc: 51101,
|
|
textovaAdresa: "Nádražní 485, 51101 Turnov",
|
|
},
|
|
};
|
|
|
|
function mockFetchOnce(status: number, body?: unknown) {
|
|
return vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
json: async () => body,
|
|
} as Response);
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("aresLookupByIco", () => {
|
|
it("maps a subject to the normalized prefill shape", async () => {
|
|
mockFetchOnce(200, SUBJECT);
|
|
const res = await aresLookupByIco("22599851");
|
|
expect(res).toEqual({
|
|
ico: "22599851",
|
|
dic: "CZ22599851",
|
|
name: "BOHA Automation s.r.o.",
|
|
street: "Nádražní 485",
|
|
city: "Turnov",
|
|
postal_code: "511 01",
|
|
country: "Česká republika",
|
|
});
|
|
});
|
|
|
|
it("builds Prague-style orientation numbers as 'Ulice 485/12a'", async () => {
|
|
mockFetchOnce(200, {
|
|
...SUBJECT,
|
|
sidlo: {
|
|
...SUBJECT.sidlo,
|
|
cisloOrientacni: 12,
|
|
cisloOrientacniPismeno: "a",
|
|
},
|
|
});
|
|
const res = await aresLookupByIco("22599851");
|
|
expect("street" in res && res.street).toBe("Nádražní 485/12a");
|
|
});
|
|
|
|
it("rejects a non-8-digit IČO without calling ARES", async () => {
|
|
const spy = vi.spyOn(globalThis, "fetch");
|
|
expect(await aresLookupByIco("123")).toEqual({ error: "invalid_ico" });
|
|
expect(await aresLookupByIco("abcdefgh")).toEqual({
|
|
error: "invalid_ico",
|
|
});
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("accepts an IČO with stray whitespace", async () => {
|
|
const spy = mockFetchOnce(200, SUBJECT);
|
|
const res = await aresLookupByIco(" 225 99851 ");
|
|
expect("ico" in res && res.ico).toBe("22599851");
|
|
expect(String(spy.mock.calls[0][0])).toContain("/22599851");
|
|
});
|
|
|
|
it("maps 404 to not_found and network failure to ares_unavailable", async () => {
|
|
mockFetchOnce(404);
|
|
expect(await aresLookupByIco("12345678")).toEqual({ error: "not_found" });
|
|
|
|
vi.spyOn(globalThis, "fetch").mockRejectedValueOnce(new Error("ETIMEDOUT"));
|
|
expect(await aresLookupByIco("12345678")).toEqual({
|
|
error: "ares_unavailable",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("aresSearchByName", () => {
|
|
it("maps the result list and sends the name in the POST body", async () => {
|
|
const spy = mockFetchOnce(200, { ekonomickeSubjekty: [SUBJECT] });
|
|
const res = await aresSearchByName("BOHA Automation");
|
|
expect(Array.isArray(res)).toBe(true);
|
|
if (Array.isArray(res)) {
|
|
expect(res).toHaveLength(1);
|
|
expect(res[0].name).toBe("BOHA Automation s.r.o.");
|
|
expect(res[0].postal_code).toBe("511 01");
|
|
}
|
|
const init = spy.mock.calls[0][1] as RequestInit;
|
|
expect(JSON.parse(String(init.body))).toMatchObject({
|
|
obchodniJmeno: "BOHA Automation",
|
|
pocet: 10,
|
|
});
|
|
});
|
|
|
|
it("returns [] for a blank query without calling ARES", async () => {
|
|
const spy = vi.spyOn(globalThis, "fetch");
|
|
expect(await aresSearchByName(" ")).toEqual([]);
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("maps upstream failure to ares_unavailable", async () => {
|
|
mockFetchOnce(503);
|
|
expect(await aresSearchByName("BOHA")).toEqual({
|
|
error: "ares_unavailable",
|
|
});
|
|
});
|
|
});
|