import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { NextRequest } from "next/server"; import { POST as candidatesPOST } from "@/app/api/0pt/internal/er/candidates/route"; const TOKEN = "test-er-internal-token"; let previousToken: string | undefined; function request(body: unknown, token: string | undefined = TOKEN): NextRequest { const headers: Record = { "content-type": "application/json" }; if (token !== undefined) headers["x-internal-token"] = token; return new NextRequest("http://localhost/api/0pt/internal/er/candidates", { method: "POST", headers, body: JSON.stringify(body), }); } function baseBody(records: Array<{ record_id: string; fields: Record }>) { return { tenant_id: "tenant_a", project_id: "project_a", records, blocking_fields: ["city"], compare_fields: [{ field: "name", weight: 1 }], auto_match_threshold: 0.9, review_threshold: 0.6, }; } beforeEach(() => { previousToken = process.env.INTERNAL_TOKEN; process.env.INTERNAL_TOKEN = TOKEN; }); afterEach(() => { if (previousToken === undefined) delete process.env.INTERNAL_TOKEN; else process.env.INTERNAL_TOKEN = previousToken; }); describe("POST /api/0pt/internal/er/candidates", () => { it("fails closed without the internal service token", async () => { // OBS: request(body, undefined) triggar default-parametern (skickar GILTIGT // token) — skicka tom string för "utan token" (fail-closed → 401). const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme AB", city: "Stockholm" } }, { record_id: "b", fields: { name: "Acme AB", city: "Stockholm" } }, ]), "", ), ); expect(res.status).toBe(401); }); it("never emits a score-only fuzzy company match even above auto_match_threshold", async () => { const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme Consulting AB", city: "Stockholm" } }, { record_id: "b", fields: { name: "Acme Consulting A B", city: "Stockholm" } }, ]), ), ); expect(res.status).toBe(200); const body = await res.json(); expect(body.pairs).toHaveLength(1); expect(body.pairs[0].score).toBeGreaterThanOrEqual(0.9); expect(body.pairs[0].decided_by).toBe("score"); expect(body.pairs[0].status).toBe("pending_review"); expect(body.review_queue.map((pair: { candidate_id: string }) => pair.candidate_id)).toEqual([ body.pairs[0].candidate_id, ]); }); it("allows a same-country registration number to hard-link despite punctuation differences", async () => { const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme Sweden AB", city: "Stockholm", registration_country: "SE", org_nr: "556000-0001", }, }, { record_id: "b", fields: { name: "Acme Sverige", city: "Stockholm", registration_country: "se", org_nr: "5560000001", }, }, ]), ), ); expect(res.status).toBe(200); const body = await res.json(); expect(body.pairs).toHaveLength(1); expect(body.pairs[0].status).toBe("match"); expect(body.pairs[0].decided_by).toBe("0pt.company.stable-identifiers.v3"); expect(body.review_queue).toEqual([]); }); it("does not treat the same national registration number in different countries as global identity", async () => { const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme Consulting AB", city: "Stockholm", registration_country: "SE", org_nr: "123456789", }, }, { record_id: "b", fields: { name: "Acme Consulting A B", city: "Stockholm", registration_country: "NO", org_nr: "123456789", }, }, ]), ), ); expect(res.status).toBe(200); const body = await res.json(); expect(body.pairs).toHaveLength(1); expect(body.pairs[0].decided_by).toBe("score"); expect(body.pairs[0].status).toBe("pending_review"); }); it("lets conflicting stable identity evidence dominate a matching global LEI", async () => { const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme Sweden AB", city: "Stockholm", registration_country: "SE", lei: "549300EXAMPLE123", org_nr: "556000-0001", }, }, { record_id: "b", fields: { name: "Acme Sweden AB", city: "Stockholm", registration_country: "SE", lei: "549300EXAMPLE123", org_nr: "556000-9999", }, }, ]), ), ); expect(res.status).toBe(200); const body = await res.json(); expect(body.pairs).toHaveLength(1); expect(body.pairs[0].status).toBe("non_match"); expect(body.pairs[0].decided_by).toBe("0pt.company.stable-identifiers.v3"); }); it("can hard-link a globally matching LEI when no scoped identifier contradicts it", async () => { const res = await candidatesPOST( request( baseBody([ { record_id: "a", fields: { name: "Acme Holding", city: "Stockholm", lei: "549300-EXAMPLE-123" }, }, { record_id: "b", fields: { name: "ACME Holdings Group", city: "Stockholm", lei: "549300EXAMPLE123" }, }, ]), ), ); expect(res.status).toBe(200); const body = await res.json(); expect(body.pairs).toHaveLength(1); expect(body.pairs[0].status).toBe("match"); expect(body.pairs[0].decided_by).toBe("0pt.company.stable-identifiers.v3"); }); });