Files
jbergner 133fb10a93
All checks were successful
release-tag / release-image (push) Successful in 1m47s
RC-5 Versiopn: 1.6.3
2026-07-22 05:58:38 +02:00

578 lines
21 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"context"
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"net/netip"
"strings"
"testing"
"github.com/b1tsblog/license-platform/sdk/go/licenseclient"
)
func testHandler(t *testing.T) http.Handler {
t.Helper()
return testHandlerConfig(t, Config{ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de"})
}
func testHandlerConfig(t *testing.T, cfg Config) http.Handler {
t.Helper()
h, err := New(context.Background(), cfg, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatal(err)
}
return h
}
func TestPresetBadgeFrench(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/badge/research.svg?lang=fr", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "image/svg+xml") {
t.Fatalf("content type %q", ct)
}
if !strings.Contains(w.Body.String(), "Aide à la recherche") {
t.Fatal("missing localized preset")
}
}
func TestValidate(t *testing.T) {
body := `{"@context":"https://example.org/context/v1","@type":"AIUsageDeclaration","schemaVersion":"1.1","language":"de","components":{"text":{"aiExtent":"assisted","activities":["research"],"humanReview":"editorial"}},"assurance":"selfDeclared"}`
r := httptest.NewRequest(http.MethodPost, "/v1/validate", strings.NewReader(body))
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
var result map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &result); err != nil {
t.Fatal(err)
}
if result["valid"] != true {
t.Fatalf("unexpected response: %#v", result)
}
}
func TestCommunityRejectsCustomBadge(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?lang=en&badgeMessage=Custom", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusForbidden {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "pro_feature_required") {
t.Fatalf("unexpected response: %s", w.Body.String())
}
}
func TestCapabilities(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/v1/capabilities", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d", w.Code)
}
if !strings.Contains(w.Body.String(), `"edition":"community"`) {
t.Fatalf("unexpected response: %s", w.Body.String())
}
}
func TestPublicLicenseRedactsInternalMetadata(t *testing.T) {
status := publicLicense(licenseclient.Status{
Edition: "pro", Licensed: true, LicenseID: "lic-secret", Customer: "Customer Name", Product: "product",
Features: []string{"custom_text"}, Limits: map[string]int64{"seats": 3}, ExpiresAt: "2030-01-01T00:00:00Z",
Mode: "online", Source: "server", LastChecked: "2026-07-20T12:00:00Z", LeaseExpires: "2026-07-21T12:00:00Z",
Reason: "internal diagnostic", ServerURL: "https://licenses.internal.example",
})
data, err := json.Marshal(status)
if err != nil {
t.Fatal(err)
}
body := string(data)
for _, forbidden := range []string{"lic-secret", "Customer Name", "licenses.internal.example", "internal diagnostic", "lastChecked", "leaseExpiresAt", `"mode"`, `"source"`} {
if strings.Contains(body, forbidden) {
t.Fatalf("public licence status leaks %q: %s", forbidden, body)
}
}
for _, expected := range []string{`"edition":"pro"`, `"licensed":true`, `"custom_text"`, `"seats":3`} {
if !strings.Contains(body, expected) {
t.Fatalf("public licence status missing %q: %s", expected, body)
}
}
}
func TestMarketingPageGerman(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/product?lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{"KI-Nutzung transparent kennzeichnen", "Docker Compose", "JSON-LD"} {
if !strings.Contains(body, expected) {
t.Fatalf("marketing page missing %q", expected)
}
}
}
func TestMarketingPageLanguageNegotiation(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/product", nil)
r.Header.Set("Accept-Language", "fr-FR,fr;q=0.9,en;q=0.8")
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "Déclarez lusage de lIA") {
t.Fatal("missing French marketing content")
}
}
func TestInstallAlias(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/install?lang=en", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusTemporaryRedirect {
t.Fatalf("status %d", w.Code)
}
if location := w.Header().Get("Location"); location != "/product?lang=en#install" {
t.Fatalf("unexpected redirect %q", location)
}
}
func TestPricingRouteRemoved(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/pricing", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusNotFound {
t.Fatalf("status %d", w.Code)
}
}
func TestDeclarationLanguageSwitcherPreservesQuery(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&textExtent=partial&textReview=expert&coverImageExtent=partial&coverImageReview=editorial&lang=de&assurance=selfDeclared", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
`id="declaration-language"`,
`lang=en`,
`textExtent=partial`,
`textReview=expert`,
`coverImageExtent=partial`,
`hreflang="en"`,
`hreflang="x-default"`,
} {
if !strings.Contains(body, expected) {
t.Fatalf("language switcher missing %q", expected)
}
}
if !strings.Contains(body, `<option value="/declaration?`) || !strings.Contains(body, `lang=de`) || !strings.Contains(body, ` selected`) {
t.Fatal("current language is not selected")
}
}
func TestArticleBadgeUsesCalmVioletDefault(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?mode=article&textExtent=none&textReview=none&imageExtent=full&imageReview=editorial&lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("unexpected status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), `fill="#7c3aed"`) {
t.Fatalf("article badge does not use violet default: %s", w.Body.String())
}
}
func TestGeneratorExposesAssuranceSelection(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
`id="assurance"`,
`value="selfDeclared"`,
`value="technicallyRecorded"`,
`value="signed"`,
`value="verified"`,
`Nachweisgrundlage`,
} {
if !strings.Contains(body, expected) {
t.Fatalf("generator missing %q", expected)
}
}
}
func TestDeclarationUsesSelectedAssurance(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/declaration?preset=research&lang=de&assurance=technicallyRecorded", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
`"assurance":"technicallyRecorded"`,
`Als Nachweisgrundlage ist eine technische Protokollierung im Erstellungs- oder Veröffentlichungsprozess angegeben.`,
`<colgroup>`,
`scope="col"`,
} {
if !strings.Contains(body, expected) {
t.Fatalf("declaration missing %q", expected)
}
}
}
func TestSignedAssuranceExplainsScope(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/declaration?preset=summary&lang=de&assurance=signed", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "Die Signatur bestätigt nicht automatisch die inhaltliche Richtigkeit der Angaben.") {
t.Fatal("signed assurance scope is not explained")
}
}
func TestGeneratorShowsAIACTGuardrails(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
`id="legal-deepfake"`,
`id="legal-public-interest"`,
`id="legal-creative"`,
`id="responsible-role"`,
`id="responsible"`,
`id="responsible-url"`,
"formale Prüfung",
"Platzierung nach Art. 50",
"erfüllt aber nicht automatisch die Anbieterpflicht",
"Emoji-Variante",
} {
if !strings.Contains(body, expected) {
t.Fatalf("generator missing AI Act guardrail %q", expected)
}
}
}
func TestDeclarationExplainsReviewAndMachineMarkingLimits(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/declaration?preset=full&review=editorial&lang=de&responsible=Beispielredaktion&responsibleUrl=https%3A%2F%2Fexample.org%2Fimpressum", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
"Beispielredaktion",
"begründet für sich allein keine Ausnahme",
"wirksame, zuverlässige, robuste und interoperable maschinenlesbare Markierung",
} {
if !strings.Contains(body, expected) {
t.Fatalf("declaration missing AI Act guardrail %q", expected)
}
}
}
func TestBackgroundPageGerman(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/background?lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
"Warum KI-Nutzung gekennzeichnet wird",
"Artikel 50 gilt ab 2. August 2026",
"keine allgemeine Kennzeichnungspflicht",
"KI-generierter Text zu Angelegenheiten von öffentlichem Interesse",
"Leitlinien zu Transparenzpflichten",
`hreflang="en"`,
`id="background-language"`,
} {
if !strings.Contains(body, expected) {
t.Fatalf("background page missing %q", expected)
}
}
}
func TestBackgroundPageFrenchNegotiation(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/background", nil)
r.Header.Set("Accept-Language", "fr-FR,fr;q=0.9,en;q=0.8")
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "Pourquoi signaler lutilisation de lIA") {
t.Fatal("missing French background content")
}
}
func TestNavigationUsesInternalBackgroundPage(t *testing.T) {
for _, path := range []string{"/?lang=de", "/product?lang=de"} {
r := httptest.NewRequest(http.MethodGet, path, nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("%s returned %d", path, w.Code)
}
if !strings.Contains(w.Body.String(), `href="/background?lang=de"`) {
t.Fatalf("%s does not link to the internal background page", path)
}
}
}
func TestLegalPagesUseConfiguredOperatorData(t *testing.T) {
cfg := Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de",
LegalName: "Beispiel GmbH", LegalAddress: "Musterstraße 1\n10115 Berlin", LegalEmail: "datenschutz@example.org",
HostingProvider: "Beispiel Hosting GmbH", LogRetention: "7 Tage", ConsumerDisputeStatus: "not_participating",
}
h := testHandlerConfig(t, cfg)
for _, tc := range []struct {
path, expected string
}{
{"/impressum?lang=de", "Beispiel GmbH"},
{"/datenschutz?lang=de", "Die Client-IP wird nur protokolliert"},
{"/barrierefreiheit?lang=de", "Noch nicht durch eine unabhängige Stelle geprüft"},
} {
r := httptest.NewRequest(http.MethodGet, tc.path, nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("%s returned %d: %s", tc.path, w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), tc.expected) {
t.Fatalf("%s missing %q", tc.path, tc.expected)
}
if !strings.Contains(w.Body.String(), `/impressum?lang=de`) || !strings.Contains(w.Body.String(), `/datenschutz?lang=de`) {
t.Fatalf("%s missing legal footer links", tc.path)
}
}
}
func TestLegalStrictRejectsMissingConfiguration(t *testing.T) {
_, err := New(context.Background(), Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", LegalStrict: true,
}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "LEGAL_NAME") {
t.Fatalf("expected strict legal configuration error, got %v", err)
}
}
func TestSecurityHeadersAndRequestIDValidation(t *testing.T) {
cfg := Config{ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", EnableHSTS: true}
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Header.Set("X-Request-ID", "invalid request id\n")
w := httptest.NewRecorder()
testHandlerConfig(t, cfg).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
csp := w.Header().Get("Content-Security-Policy")
if !strings.Contains(csp, "script-src 'self' 'nonce-") || strings.Contains(csp, "'unsafe-inline'") {
t.Fatalf("unexpected CSP %q", csp)
}
if got := w.Header().Get("Strict-Transport-Security"); !strings.Contains(got, "max-age=") || strings.Contains(strings.ToLower(got), "includesubdomains") {
t.Fatalf("unexpected HSTS: %q", got)
}
if got := w.Header().Get("X-Request-ID"); got == "" || strings.Contains(got, " ") {
t.Fatalf("invalid response request id %q", got)
}
}
func TestMetricsDisabledByDefaultAndTokenProtected(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/metrics", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusNotFound {
t.Fatalf("disabled metrics returned %d", w.Code)
}
cfg := Config{ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", MetricsEnabled: true, MetricsToken: "a-long-random-test-token"}
h := testHandlerConfig(t, cfg)
w = httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/metrics", nil))
if w.Code != http.StatusUnauthorized {
t.Fatalf("unprotected metrics returned %d", w.Code)
}
r = httptest.NewRequest(http.MethodGet, "/metrics", nil)
r.Header.Set("Authorization", "Bearer a-long-random-test-token")
w = httptest.NewRecorder()
h.ServeHTTP(w, r)
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "ai_disclosure_http_requests_total") {
t.Fatalf("protected metrics returned %d: %s", w.Code, w.Body.String())
}
}
func TestMetricsCannotBeEnabledWithoutToken(t *testing.T) {
_, err := New(context.Background(), Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", MetricsEnabled: true,
}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "METRICS_TOKEN") {
t.Fatalf("expected metrics token validation error, got %v", err)
}
}
func TestInvalidBooleanEnvironmentValueFailsClosed(t *testing.T) {
t.Setenv("LEGAL_STRICT", "definitely")
cfg := ConfigFromEnv()
_, err := New(context.Background(), cfg, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "LEGAL_STRICT must be true or false") {
t.Fatalf("expected environment validation error, got %v", err)
}
}
func TestLegalEmailMustBePlainAddress(t *testing.T) {
_, err := New(context.Background(), Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", LegalEmail: "Name <legal@example.org>",
}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "LEGAL_EMAIL") {
t.Fatalf("expected legal email validation error, got %v", err)
}
}
func TestExternalURLsMustUseHTTPOrHTTPS(t *testing.T) {
_, err := New(context.Background(), Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", ContactURL: "javascript:alert(1)",
}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "CONTACT_URL") {
t.Fatalf("expected contact URL validation error, got %v", err)
}
}
func TestValidateRejectsOversizedBody(t *testing.T) {
body := `{"padding":"` + strings.Repeat("x", (1<<20)+1) + `"}`
r := httptest.NewRequest(http.MethodPost, "/v1/validate", strings.NewReader(body))
r.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusRequestEntityTooLarge {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
}
func TestValidateRejectsNonJSONContentType(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/v1/validate", strings.NewReader(`{}`))
r.Header.Set("Content-Type", "text/plain")
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusUnsupportedMediaType {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
}
func TestClientIPUsesNearestUntrustedForwardedAddress(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "https://example.org/", nil)
r.RemoteAddr = "10.0.0.2:443"
r.Header.Set("X-Forwarded-For", "198.51.100.200, 203.0.113.50, 10.0.0.3")
trusted := []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")}
if got := clientIP(r, true, trusted); got != "203.0.113.50" {
t.Fatalf("unexpected client IP %q", got)
}
}
func TestTrustProxyRequiresExplicitCIDRs(t *testing.T) {
_, err := New(context.Background(), Config{
ListenAddress: ":0", BaseURL: "https://example.org", PublicName: "Test", DefaultLanguage: "de", TrustProxy: true,
}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err == nil || !strings.Contains(err.Error(), "TRUSTED_PROXY_CIDRS") {
t.Fatalf("expected trusted proxy validation error, got %v", err)
}
}
func TestEmojiThemeBadgeEndpoint(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/badge/summary.svg?lang=de&theme=emoji", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{
`width="64" height="64"`,
`data-theme="emoji"`,
`data-icon="summary"`,
`fill="#b7791f"`,
`Inhaltliche Zusammenfassung`,
} {
if !strings.Contains(body, expected) {
t.Fatalf("emoji badge missing %q: %s", expected, body)
}
}
}
func TestGeneratorOffersEmojiTheme(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/?lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
const expected = `<option value="emoji">Emoji (quadratisch)</option>`
if !strings.Contains(w.Body.String(), expected) {
t.Fatalf("generator missing %q", expected)
}
}
func TestPublicInterestBadgeUsesExplicitDisclosureWithoutExemption(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?mode=article&textExtent=full&textReview=none&legalContext=publicInterestText&lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "KI-generierte / bearbeitete Inhalte") {
t.Fatalf("missing explicit legal disclosure wording: %s", w.Body.String())
}
}
func TestPublicInterestBadgeCanRemainVoluntaryWithSubstantiveReviewAndResponsibility(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?mode=article&textExtent=partial&textReview=expert&legalContext=publicInterestText&responsible=Beispielredaktion&lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "Artikeltransparenz") {
t.Fatalf("expected voluntary article wording where possible exception is documented: %s", w.Body.String())
}
}
func TestDeepfakeDeclarationShowsCautiousAssessment(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&imageExtent=full&imageReview=none&legalContext=deepfake&lang=de", nil)
w := httptest.NewRecorder()
testHandler(t).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, expected := range []string{"Vorsichtige Art.-50-Einschätzung", "spricht vieles dafür", "Deepfake / realitätsähnliche KI-Manipulation"} {
if !strings.Contains(body, expected) {
t.Fatalf("missing %q: %s", expected, body)
}
}
}