import assert from "node:assert/strict"; import test from "node:test"; import { MostovikOrganizationsClient, type ApiErrorPayload, type FetchLike, type GenericSourcePayload, type Organization, type OrganizationListResponse, } from "../src/index.js"; function jsonResponse(payload: unknown, init: ResponseInit = {}): Response { return new Response(JSON.stringify(payload), { status: init.status ?? 200, headers: { "content-type": "application/json", ...init.headers, }, }); } test("listOrganizations serializes all supported organization filters", async () => { const calls: RequestInfo[] = []; const fetchImpl: FetchLike = async (input) => { calls.push(input); const payload: OrganizationListResponse = { success: true, data: [], errors: null, meta: { pagination: { page: 2, page_size: 50, total_count: 0, total_pages: 0, has_next: false, has_previous: true, }, }, }; return jsonResponse(payload); }; const client = new MostovikOrganizationsClient({ baseUrl: "https://api.example.test/", fetch: fetchImpl, headers: { Authorization: "Bearer static-token" }, }); const response = await client.listOrganizations({ page: 2, page_size: 50, search: "мост", ordering: "-name", name: "Северный", inn: "7711111111", kpp: "771101001", ogrn: "1027700132111", ogrip: "304500116000157", registry: "8e7a3cb8-6fb2-43a8-b847-62d84ea9f34f", registry_name: "Росатом", has_registry: false, has_industrial: true, has_industrial_products: false, has_manufactures: true, has_inspections: false, has_procurements: true, has_procurements_44fz: false, has_procurements_223fz: true, has_contracts: false, has_unfair_suppliers: true, has_fas_goz: false, has_arbitration: true, has_fedresurs_bankruptcy: false, has_fstec: true, has_vacancies: true, has_trudvsem: false, has_fns_reports: false, data: ["industrial", "fns_reports"], exclude_data_sources: "vacancies", }); assert.equal(response.success, true); assert.equal(calls.length, 1); const url = new URL(String(calls[0])); assert.equal(url.origin, "https://api.example.test"); assert.equal(url.pathname, "/api/v2/organizations/"); assert.equal(url.searchParams.get("page"), "2"); assert.equal(url.searchParams.get("page_size"), "50"); assert.equal(url.searchParams.get("ordering"), "-name"); assert.equal(url.searchParams.get("has_registry"), "false"); assert.deepEqual(url.searchParams.getAll("data"), ["industrial", "fns_reports"]); assert.equal(url.searchParams.get("exclude_data_sources"), "vacancies"); assert.equal(url.searchParams.get("has_vacancies"), "true"); assert.equal(url.searchParams.get("has_trudvsem"), "false"); }); test("getOrganization requests detail endpoint and returns typed source data", async () => { const calls: RequestInfo[] = []; const uid = "5dc142b3-dcf4-4b90-807a-a80e883dd05c"; const payload: Organization = { uid, name: "ООО \"Данные\"", normalized_name: "ООО \"Данные\"", inn: "7777777777", kpp: "777701001", ogrn: "1027700132777", ogrip: "", registries: [ { id: "0b85bf08-c6d9-4c07-98c3-a057630e3a35", name: "Росатом ГОЗ", }, ], data_sources: [ { source: "industrial", count: 1, }, { source: "fns_reports", count: 1, }, ], data: { industrial: [ { id: 1, load_batch: 10, issue_date: "01.01.2025", issue_date_normalized: "2025-01-01", certificate_number: "CERT-1", expiry_date: "", expiry_date_normalized: null, certificate_file_url: "https://example.test/cert.pdf", organisation_name: "ООО \"Данные\"", inn: "7777777777", ogrn: "1027700132777", registry_organization: null, created_at: "2026-05-01T10:00:00Z", updated_at: "2026-05-01T10:00:00Z", }, ], fns_reports: [ { id: 2, external_id: "fin-1", ogrn: "1027700132777", registry_organization: null, file_name: "fin.xlsx", file_hash: "a".repeat(64), load_batch: 11, status: "success", source: "api", error_message: "", created_at: "2026-05-01T10:00:00Z", updated_at: "2026-05-01T10:00:00Z", lines_count: 1, lines: { "2024": { active: { "1100": { form_code: "1", name: "Внеоборотные активы", period_start: 100, period_end: 200, }, }, }, }, }, ], }, }; const fetchImpl: FetchLike = async (input) => { calls.push(input); return jsonResponse(payload); }; const client = new MostovikOrganizationsClient({ baseUrl: "https://api.example.test", fetch: fetchImpl, }); const organization = await client.getOrganization(uid, { data_sources: ["industrial", "fns_reports"], }); assert.equal(organization.uid, uid); assert.equal(organization.data.industrial?.[0]?.certificate_number, "CERT-1"); assert.equal( organization.data.fns_reports?.[0]?.lines["2024"]?.active?.["1100"]?.period_end, 200, ); const url = new URL(String(calls[0])); assert.equal(url.pathname, `/api/v2/organizations/${uid}/`); assert.deepEqual(url.searchParams.getAll("data_sources"), [ "industrial", "fns_reports", ]); }); test("throws ApiClientError with status and payload for non-2xx responses", async () => { const errorPayload: ApiErrorPayload = { success: false, data: null, errors: [ { code: "validation_error", message: "Validation failed", details: { fields: { data: ["Unknown data source(s): unknown"], }, }, }, ], meta: { request_id: "test-request-id", }, }; const fetchImpl: FetchLike = async () => jsonResponse(errorPayload, { status: 400 }); const client = new MostovikOrganizationsClient({ baseUrl: "https://api.example.test", fetch: fetchImpl, }); await assert.rejects( () => client.getOrganization("uid", { data: "industrial" }), (error: unknown) => { assert.equal(error instanceof Error, true); assert.equal((error as { name?: string }).name, "ApiClientError"); assert.equal((error as { status?: number }).status, 400); assert.deepEqual((error as { payload?: ApiErrorPayload }).payload, errorPayload); return true; }, ); }); test("generic source payload is represented as a typed JSON object", () => { const payload: GenericSourcePayload = { provider: "checko", target: { inn: "7711111199", ogrn: "1027700132199", }, suppliers: [ { inn: "7722222299", amount: 1000, }, ], }; assert.equal(payload.provider, "checko"); });