Files
pitchfast/tests/german-copy-guard.test.ts

299 lines
12 KiB
TypeScript

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 accepts live audit copy with noun suggestion and collaborative close", () => {
const result = validateCustomerFacingCopy({
auditSummary:
"Ich habe beobachtet, dass die Website von Diehl & Pape Rechtsanwälte zwar durch ihre klare Spezialisierung und umfassenden Kontaktinformationen überzeugt, jedoch durch langsame Ladezeiten und sichtbare Inhaltsverschiebungen beim Laden an Nutzerkomfort verliert. Ich schlage vor, gezielt die Ladegeschwindigkeit zu optimieren und das Seitenlayout stabil zu gestalten, um das Vertrauen potenzieller Mandanten zu stärken und die Nutzerbindung nachhaltig zu erhöhen.",
auditBody:
"Ich habe die Website von Diehl & Pape Rechtsanwälte genau betrachtet und festgestellt, dass die langsamen Ladezeiten und die sichtbaren Inhaltsverschiebungen beim Laden den ersten Eindruck deutlich beeinträchtigen. Mir ist aufgefallen, wie wichtig gerade für eine erfahrene Kanzlei mit klarer Spezialisierung ein reibungsloses Nutzererlebnis ist, um Vertrauen bei potenziellen Mandanten aufzubauen. Deshalb schlage ich vor, gezielt die Ladegeschwindigkeit zu optimieren und die Stabilität des Seitenlayouts zu verbessern.",
emailSubject:
"Ich habe beobachtet, dass die Website von Diehl & Pape Rechtsanwälte durch langsame Ladezeiten und sichtbare Inhaltsverschiebungen die Nutzererfahrung beeinträchtigt.",
emailBody:
"Ich habe die Website von Diehl & Pape Rechtsanwälte genau unter die Lupe genommen und festgestellt, dass die langsamen Ladezeiten und die sichtbaren Inhaltsverschiebungen beim Laden den ersten Eindruck deutlich trüben. Mein konkreter Vorschlag: Eine gezielte Optimierung der Ladegeschwindigkeit und eine Stabilisierung des Seitenlayouts könnten die Nutzerzufriedenheit erheblich steigern.",
callScript: {
openingLine:
"Ich habe die Website von Diehl & Pape Rechtsanwälte genau unter die Lupe genommen und dabei ein wichtiges Verbesserungspotenzial entdeckt.",
callScript: [
"Mir ist aufgefallen, dass die Seite beim Laden deutlich sichtbare Inhaltsverschiebungen zeigt.",
"Ich schlage vor, gezielt die Ladegeschwindigkeit zu optimieren und die Stabilität des Seitenlayouts zu verbessern.",
],
closeLine:
"Lassen Sie uns gemeinsam diese technischen Hürden beseitigen und Ihre Website zu einem überzeugenden Aushängeschild Ihrer Expertise machen.",
},
followUp:
"Ich habe beobachtet, dass die Website von Diehl & Pape Rechtsanwälte durch langsame Ladezeiten an Nutzerkomfort verliert. Mein konkreter Vorschlag ist, die Ladegeschwindigkeit gezielt zu optimieren und die Stabilität des Seitenlayouts sicherzustellen.",
});
assert.equal(result.passed, true);
assert.deepEqual(result.issues, []);
});
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,
);
});