import assert from "node:assert/strict"; import test from "node:test"; import { validateCallScriptCopy, validateCustomerFacingCopy, validateFollowUpCopy, } from "../lib/ai/german-copy-guard"; const validPayload = { auditSummary: "Ich habe euren Webauftritt geprüft. Mir ist aufgefallen, dass die Kontaktseite nicht klar erreichbar ist. Ich empfehle, den Kontaktbereich im Header sichtbar zu platzieren.", auditBody: "Mir ist aufgefallen, dass die Kontaktseite nur am Ende der Startseite eingebettet ist. Ich empfehle, sie im Kopfbereich direkt zu platzieren.", emailSubject: "Kurzes Feedback zu eurem Webauftritt", emailBody: "Hallo, ich habe eure Seite betrachtet und festgestellt, dass die Kontaktoptionen auf mobilen Geräten schwer zu finden sind. Ich empfehle, einen klar sichtbaren Button einzubauen.", callScript: { openingLine: "Hallo, ich bin Matthias von der Webberatung.", callScript: [ "Ich habe eure Website geprüft und gesehen, dass der Kontaktbereich nicht sofort sichtbar ist.", "Ich schlage vor, den Kontakt-Button in den Header zu setzen und die Mobil-Ansicht anzupassen.", ], closeLine: "Wenn das hilfreich klingt, kann ich euch in zwei Minuten die nächsten Schritte skizzieren.", }, followUp: "Mir ist noch etwas aufgefallen: Auf der Mobilversion fehlt ein klarer Termin- oder Kontakthinweis. Ich schlage vor, diesen Bereich oberhalb der Leistungstexte deutlich zu markieren.", }; test("validateCustomerFacingCopy passes clean German outreach and audit copy", () => { const result = validateCustomerFacingCopy(validPayload); assert.equal(result.passed, true); assert.equal(result.issues.length, 0); }); test("validateCustomerFacingCopy rejects likely non-German copy and reports language", () => { const result = validateCustomerFacingCopy({ ...validPayload, emailBody: "Your site looks very strong, and your performance score is 0.82 with good Lighthouse numbers.", }); assert.equal(result.passed, false); assert.equal( result.issues.some((issue) => issue.field === "emailBody" && issue.rule === "not_german", ), true, ); }); test("validateCustomerFacingCopy flags short English artifact-like snippets in content fields", () => { const shortInputs: Array<{ field: "auditSummary" | "auditBody" | "emailBody" | "followUp"; value: string; }> = [ { field: "emailBody", value: "quick audit" }, { field: "auditBody", value: "bad website" }, { field: "followUp", value: "AI report" }, ]; for (const { field, value } of shortInputs) { const payload = { ...validPayload, [field]: value }; const result = validateCustomerFacingCopy(payload as typeof validPayload); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === field && issue.rule === "not_german", ), true, `Expected ${field} short snippet "${value}" to fail german language check.`, ); } }); test("validateCustomerFacingCopy requires Ich-form in applicable customer-facing fields", () => { const result = validateCustomerFacingCopy({ ...validPayload, auditBody: "Ihre Seite hat eine gute Struktur. Der Kontaktbereich sollte klarer werden.", followUp: "Die Website sollte verbessert werden. Setzt bitte einen Kontaktbutton.", }); const hasAuditIssue = result.issues.some( (issue) => issue.field === "auditBody" && issue.rule === "missing_ich_form", ); const hasFollowUpIssue = result.issues.some( (issue) => issue.field === "followUp" && issue.rule === "missing_ich_form", ); assert.equal(result.passed, false); assert.equal(hasAuditIssue, true); assert.equal(hasFollowUpIssue, true); }); test("validateCustomerFacingCopy blocks PageSpeed-like score artifacts in public text", () => { const result = validateCustomerFacingCopy({ ...validPayload, auditSummary: "Aus dem PageSpeed-Check ergibt sich ein score: 0.82 im Bereich Performance.", }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "auditSummary" && issue.rule === "pagespeed_score_artifact", ), true, ); }); test("validateCustomerFacingCopy blocks price/currency mention", () => { const result = validateCustomerFacingCopy({ ...validPayload, callScript: { ...validPayload.callScript, callScript: [ "Der Kontaktpunkt ist gut sichtbar.", "Ihr Paket kostet nur 99 € pro Monat.", "Ich habe den Kontaktpunkt geprüft und schlage vor, ihn in der Headerzeile zu fixieren.", ], }, }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "callScript.callScript[1]" && issue.rule === "price_mention", ), true, ); }); test("validateCustomerFacingCopy rejects generic AI slop language", () => { const result = validateCustomerFacingCopy({ ...validPayload, emailBody: "Unsere maßgeschneiderte, nahtlose, innovative Lösung hebt Ihre Sichtbarkeit auf ein neues Level und ist wirklich disruptive.", }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "emailBody" && issue.rule === "generic_ai_slop", ), true, ); }); test("validateCustomerFacingCopy flags accusatory tone", () => { const result = validateCustomerFacingCopy({ ...validPayload, auditBody: "Ihre Website ist katastrophal und wirkt absolut unprofessionell. Das sollte dringend geändert werden.", }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "auditBody" && issue.rule === "hostile_tone", ), true, ); }); test("validateCustomerFacingCopy strips technical artifacts like model ids and raw JSON", () => { const result = validateCustomerFacingCopy({ ...validPayload, followUp: 'Ich habe folgende Diagnose: {"score": 0.8, "lighthouseResult": "ok", "storageId": "rawstorageid_abc123"}', }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "followUp" && issue.rule === "raw_technical_artifact", ), true, ); }); test("validateCustomerFacingCopy enforces observation + suggestion style", () => { const result = validateCustomerFacingCopy({ ...validPayload, emailBody: "Deine Website ist großartig, tolle Arbeit.", }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "emailBody" && issue.rule === "missing_observation_or_suggestion", ), true, ); }); test("validateCustomerFacingCopy is permissive for phone numbers and date values", () => { const result = validateCustomerFacingCopy({ auditSummary: "Ich habe gesehen, dass eure Kontaktseite am 12.02.2026 aktualisiert wurde. Ich empfehle, den Kontaktbereich als Nächstes im Header zu verbessern.", auditBody: "Mir ist aufgefallen, dass die Telefonnummer 0201 123456 in der Fußzeile steht. Ich empfehle, sie zusätzlich im Header zu platzieren.", emailSubject: "Kurzes Feedback zu eurem Terminplan", emailBody: "Hallo, ich habe euren Webauftritt geprüft und habe gesehen, dass Termine auf der Seite mit dem Datum 12. Oktober erwähnt sind. Ich empfehle, diese Terminangabe im Header stärker hervorzuheben.", callScript: { openingLine: "Hallo, ich bin Matthias und ich habe eure Seite geprüft.", callScript: [ "Ich habe auf eurer Seite gesehen, dass der Kontaktbutton erst sehr weit unten erscheint.", "Mir ist aufgefallen, dass hier noch eine kleine Verbesserung fehlt; ich schlage vor, den Kontaktbereich nach oben zu ziehen.", ], closeLine: "Dann nehme ich das Thema in den nächsten Schritt mit auf.", }, followUp: "Mir ist am 12. Oktober aufgefallen, dass die Telefonnummer 030 1234567 schon gut auffindbar ist; ich schlage vor, eine kleine Sichtbarkeitsanpassung vorzunehmen.", }); assert.equal(result.passed, true); }); test("validateCallScriptCopy validates each script line individually and returns field paths", () => { const result = validateCallScriptCopy({ openingLine: "Hallo, ich bin Matthias.", callScript: [ "{" + '"score": 0.82, "rawstorageid":"abc123"' + "}", "Ich habe auf der Seite gesehen, dass der Kontaktbutton fehlt.", "Mir fehlt noch ein konkreter Verbesserungsschritt.", ], closeLine: "Schöne Grüße", }); assert.equal(result.passed, false); assert.equal( result.issues.some( (issue) => issue.field === "callScript.callScript[0]" && issue.rule === "raw_technical_artifact", ), true, ); }); test("validateFollowUpCopy enforces ich-form and guard output shape", () => { const result = validateFollowUpCopy({ message: "Hier ist der Inhalt für das Follow-up.", }); assert.equal(result.passed, false); assert.equal(result.issues.length > 0, true); assert.equal( result.issues.some( (issue) => issue.field === "followUp" && issue.rule === "missing_ich_form", ), true, ); });