feat: add OpenRouter audit generation pipeline
This commit is contained in:
270
tests/german-copy-guard.test.ts
Normal file
270
tests/german-copy-guard.test.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user