165 lines
5.6 KiB
TypeScript
165 lines
5.6 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import {
|
|
buildLlmDossier,
|
|
createPseudonymizer,
|
|
reversePseudonymsInText,
|
|
reversePseudonymsInDossier,
|
|
} from "./privacy";
|
|
import type { LlmDossier, SummaryData } from "./privacy";
|
|
|
|
const sample: SummaryData = {
|
|
kontakt: {
|
|
unternehmen: {
|
|
name: "Muster GmbH",
|
|
adresse: "Hauptstrasse 1",
|
|
plz: "80331 Muenchen",
|
|
telefon: "+49 89 123456",
|
|
email: "kontakt@muster.de",
|
|
web: "www.muster.de",
|
|
},
|
|
ansprechpartner: {
|
|
name: "Max Mustermann",
|
|
telefon: "0170 1234567",
|
|
email: "max.mustermann@muster.de",
|
|
},
|
|
},
|
|
unternehmen: {
|
|
branche: "Maschinenbau",
|
|
gruendungsjahr: "1998",
|
|
anzahl_mitarbeiter: "120",
|
|
interne_notiz: "Nicht fuer LLM",
|
|
},
|
|
fragen: {
|
|
frage1: {
|
|
label: "Frage 1",
|
|
antwort: "Max Mustermann beschreibt die Entwicklung der Muster GmbH. Muster investiert weiter. Kontakt: max.mustermann@muster.de.",
|
|
fragen: [
|
|
{
|
|
id: "frage1_1",
|
|
text: "Was macht das Unternehmen aus?",
|
|
antwort: "Die Muster GmbH ist unter +49 89 123456 erreichbar.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
kriterium: {
|
|
robustheit_resilienz: {
|
|
label: "Robustheit",
|
|
antwort: "Die Muster GmbH hat ein Risikomanagement etabliert. Muster nutzt Fruehwarnprozesse.",
|
|
},
|
|
},
|
|
};
|
|
|
|
test("builds a minimized dossier without contact data or unknown company fields", () => {
|
|
const dossier = buildLlmDossier(sample);
|
|
const serialized = JSON.stringify(dossier);
|
|
|
|
expect(dossier.unternehmen).toEqual({
|
|
branche: "Maschinenbau",
|
|
gruendungsjahr: "1998",
|
|
anzahl_mitarbeiter: "120",
|
|
});
|
|
expect(serialized).not.toContain("kontakt");
|
|
expect(serialized).not.toContain("interne_notiz");
|
|
expect(serialized).not.toContain("Hauptstrasse");
|
|
});
|
|
|
|
test("pseudonymizes known personal and contact values in LLM-bound strings", () => {
|
|
const privacy = createPseudonymizer(sample);
|
|
const prepared = privacy.pseudonymizeDossier(buildLlmDossier(sample));
|
|
const serialized = JSON.stringify(prepared.safe);
|
|
|
|
expect(serialized).toContain("[PERSON_1]");
|
|
expect(serialized).toContain("[UNTERNEHMEN]");
|
|
expect(serialized).toContain("[EMAIL_2]");
|
|
expect(serialized).toContain("[TELEFON_1]");
|
|
expect(serialized).not.toContain("Max Mustermann");
|
|
expect(serialized).not.toContain("Muster GmbH");
|
|
expect(serialized).not.toContain("Muster investiert");
|
|
expect(serialized).not.toContain("Muster nutzt");
|
|
expect(serialized).not.toContain("max.mustermann@muster.de");
|
|
expect(prepared.audit.mode).toBe("minimized+pseudonymized");
|
|
expect(prepared.audit.companyName).toBe("Muster GmbH");
|
|
expect(prepared.audit.replacements.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("reversePseudonymsInText restores original values from known replacements", () => {
|
|
const text = "[PERSON_1] von [UNTERNEHMEN] nutzt [EMAIL_2].";
|
|
const reversed = reversePseudonymsInText(text, [
|
|
{ value: "Max Mustermann", type: "person", placeholder: "[PERSON_1]" },
|
|
{ value: "Muster GmbH", type: "company", placeholder: "[UNTERNEHMEN]" },
|
|
{ value: "max.mustermann@muster.de", type: "email", placeholder: "[EMAIL_2]" },
|
|
]);
|
|
|
|
expect(reversed).toBe("Max Mustermann von Muster GmbH nutzt max.mustermann@muster.de.");
|
|
});
|
|
|
|
test("reversePseudonyms works via the Pseudonymizer API", () => {
|
|
const privacy = createPseudonymizer(sample);
|
|
const reversed = privacy.reversePseudonyms("[PERSON_1] arbeitet bei [UNTERNEHMEN].");
|
|
|
|
expect(reversed).toContain("Max Mustermann");
|
|
expect(reversed).toContain("Muster GmbH");
|
|
});
|
|
|
|
test("reversePseudonymsInText handles text without placeholders", () => {
|
|
const text = "Kein Platzhalter hier.";
|
|
const reversed = reversePseudonymsInText(text, [
|
|
{ value: "Max Mustermann", type: "person", placeholder: "[PERSON_1]" },
|
|
]);
|
|
expect(reversed).toBe(text);
|
|
});
|
|
|
|
test("reversePseudonymsInDossier reverses nested text fields including zusammenfassung", () => {
|
|
const dossier: LlmDossier = {
|
|
fragen: {
|
|
frage1: {
|
|
label: "[PERSON_1] von [UNTERNEHMEN] beantwortet",
|
|
zusammenfassung: "[PERSON_1] arbeitet bei [UNTERNEHMEN] und nutzt [EMAIL_2].",
|
|
fragen: [
|
|
{ id: "f1", text: "Was macht [UNTERNEHMEN]?", antwort: "[PERSON_1] sagt [EMAIL_2]." },
|
|
],
|
|
},
|
|
},
|
|
kriterium: {
|
|
robustheit: {
|
|
label: "Robustheit von [PERSON_1]",
|
|
zusammenfassung: "[UNTERNEHMEN] ist robust.",
|
|
},
|
|
},
|
|
unternehmen: {
|
|
referenzen: "Referenz von [PERSON_1] bei [UNTERNEHMEN].",
|
|
},
|
|
};
|
|
|
|
const reversed = reversePseudonymsInDossier(dossier, [
|
|
{ value: "Max Mustermann", type: "person", placeholder: "[PERSON_1]" },
|
|
{ value: "Muster GmbH", type: "company", placeholder: "[UNTERNEHMEN]" },
|
|
{ value: "max@muster.de", type: "email", placeholder: "[EMAIL_2]" },
|
|
]);
|
|
|
|
expect(reversed.fragen?.frage1?.zusammenfassung).toBe(
|
|
"Max Mustermann arbeitet bei Muster GmbH und nutzt max@muster.de.",
|
|
);
|
|
expect(reversed.fragen?.frage1?.fragen?.[0]?.antwort).toBe(
|
|
"Max Mustermann sagt max@muster.de.",
|
|
);
|
|
expect(reversed.fragen?.frage1?.label).toBe(
|
|
"Max Mustermann von Muster GmbH beantwortet",
|
|
);
|
|
expect(reversed.kriterium?.robustheit?.zusammenfassung).toBe(
|
|
"Muster GmbH ist robust.",
|
|
);
|
|
expect(reversed.unternehmen?.referenzen).toBe(
|
|
"Referenz von Max Mustermann bei Muster GmbH.",
|
|
);
|
|
});
|
|
|
|
test("reversePseudonymsInText handles edge case with numeric suffixes in text", () => {
|
|
const text = "[PERSON_1] und [PERSON_1].";
|
|
const reversed = reversePseudonymsInText(text, [
|
|
{ value: "Max Mustermann", type: "person", placeholder: "[PERSON_1]" },
|
|
]);
|
|
expect(reversed).toBe("Max Mustermann und Max Mustermann.");
|
|
});
|