package declaration import ( "errors" "net/url" "strings" "testing" ) func TestPresetQuery(t *testing.T) { v := url.Values{"preset": {"research"}, "subject": {"https://example.org/post"}, "lang": {"fr"}} d, err := NewFromQuery(v, "https://example.org/context/v1") if err != nil { t.Fatal(err) } c := d.Components["text"] if c.AIExtent != "assisted" || len(c.Activities) != 1 || c.Activities[0] != "research" { t.Fatalf("unexpected component: %#v", c) } if d.Language != "fr" { t.Fatalf("unexpected language: %s", d.Language) } if c.HumanReview != "none" { t.Fatalf("preset inferred human review=%q", c.HumanReview) } } func TestNoAIRejectsActivities(t *testing.T) { d := Declaration{Context: "https://example.org/context/v1", Type: "AIUsageDeclaration", SchemaVersion: SchemaVersion, Language: "de", Assurance: "selfDeclared", Components: map[string]Component{"text": {AIExtent: "none", Activities: []string{"research"}, HumanReview: "editorial"}}} if Validate(d) == nil { t.Fatal("expected validation error") } } func TestCustomRequiresPro(t *testing.T) { v := url.Values{"preset": {"research"}, "lang": {"en"}, "customTitle": {"Custom"}} _, err := NewFromQuery(v, "https://example.org/context/v1") if !errors.Is(err, ErrCustomTextRequiresPro) { t.Fatalf("unexpected error: %v", err) } d, err := NewFromQueryWithOptions(v, "https://example.org/context/v1", ParseOptions{AllowCustomText: true, DefaultLanguage: "en"}) if err != nil { t.Fatal(err) } if d.Presentation == nil || d.Presentation.Title != "Custom" { t.Fatalf("missing presentation: %#v", d.Presentation) } } func TestLegacySchemaValidation(t *testing.T) { d := Declaration{Context: "https://example.org/context/v1", Type: "AIUsageDeclaration", SchemaVersion: LegacySchemaVersion, Language: "en", Assurance: "selfDeclared", Components: map[string]Component{"text": {AIExtent: "assisted", HumanReview: "editorial"}}} if err := Validate(d); err != nil { t.Fatalf("legacy declaration should remain valid: %v", err) } d.Presentation = &Presentation{Title: "Not allowed in 1.0"} if Validate(d) == nil { t.Fatal("expected presentation to require schema 1.1") } } func TestArticleDeclarationFromQuery(t *testing.T) { q := url.Values{ "mode": {"article"}, "lang": {"de"}, "textExtent": {"none"}, "imageExtent": {"full"}, "researchExtent": {"assisted"}, } d, err := NewFromQuery(q, "https://example.org/context/v1") if err != nil { t.Fatal(err) } if len(d.Components) != 3 { t.Fatalf("components=%d", len(d.Components)) } if d.Components["text"].AIExtent != "none" { t.Fatalf("unexpected text extent") } if d.Components["text"].HumanReview != "none" { t.Fatalf("no-AI component review=%q, want none", d.Components["text"].HumanReview) } if d.Components["research"].HumanReview != "none" { t.Fatalf("AI-assisted component review=%q, want no inferred review", d.Components["research"].HumanReview) } if got := d.Components["research"].Activities; len(got) != 1 || got[0] != "research" { t.Fatalf("research activities=%v", got) } if got := d.Components["image"].Activities; len(got) != 1 || got[0] != "imageGeneration" { t.Fatalf("image activities=%v", got) } } func TestArticleDeclarationKeepsPerComponentReviews(t *testing.T) { q := url.Values{ "mode": {"article"}, "lang": {"de"}, "textExtent": {"assisted"}, "textReview": {"basic"}, "imageExtent": {"full"}, "imageReview": {"expert"}, } d, err := NewFromQuery(q, "https://example.org/context/v1") if err != nil { t.Fatal(err) } if got := d.Components["text"].HumanReview; got != "basic" { t.Fatalf("text review=%q", got) } if got := d.Components["image"].HumanReview; got != "expert" { t.Fatalf("image review=%q", got) } } func TestAssuranceFromQuery(t *testing.T) { for _, assurance := range []string{"selfDeclared", "technicallyRecorded", "signed", "verified"} { d, err := NewFromQuery(url.Values{"preset": {"research"}, "lang": {"de"}, "assurance": {assurance}}, "https://example.org/context/v1") if err != nil { t.Fatalf("assurance %s: %v", assurance, err) } if d.Assurance != assurance { t.Fatalf("assurance=%q, want %q", d.Assurance, assurance) } } } func TestEditorialResponsibilityRequiresName(t *testing.T) { _, err := NewFromQuery(url.Values{ "preset": {"research"}, "responsibleUrl": {"https://example.org/impressum"}, }, "https://example.org/context") if err == nil || !strings.Contains(err.Error(), "editorialResponsibility.name is required") { t.Fatalf("expected editorial responsibility name validation error, got %v", err) } } func TestLegalContextPublicInterestRequiresAIText(t *testing.T) { _, err := NewFromQuery(url.Values{ "mode": {"article"}, "lang": {"de"}, "textExtent": {"none"}, "legalContext": {"publicInterestText"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "publicInterestText requires a text component with AI involvement") { t.Fatalf("expected public-interest consistency error, got %v", err) } } func TestLegalContextDeepfakeRequiresAIMedia(t *testing.T) { _, err := NewFromQuery(url.Values{ "mode": {"article"}, "lang": {"de"}, "textExtent": {"full"}, "legalContext": {"deepfake"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "deepfake requires an image, audio or video component with AI involvement") { t.Fatalf("expected deepfake consistency error, got %v", err) } } func TestLegalContextAndResponsibilityAreSerialized(t *testing.T) { d, err := NewFromQuery(url.Values{ "mode": {"article"}, "lang": {"de"}, "textExtent": {"partial"}, "textReview": {"expert"}, "legalContext": {"publicInterestText"}, "responsibleRole": {"publisher"}, "responsible": {"Beispielredaktion"}, }, "https://example.org/context/v1") if err != nil { t.Fatal(err) } if d.SchemaVersion != "1.3" || d.LegalContext == nil || len(d.LegalContext.Categories) != 1 { t.Fatalf("unexpected legal context: %#v", d) } if d.EditorialResponsibility == nil || !d.EditorialResponsibility.Assumed || d.EditorialResponsibility.Role != "publisher" { t.Fatalf("responsibility not explicitly assumed: %#v", d.EditorialResponsibility) } } func TestExtendedPublicationAndLegalContextAreSerialized(t *testing.T) { d, err := NewFromQuery(url.Values{ "preset": {"full"}, "lang": {"de"}, "author": {"Ada Beispiel"}, "authorUrl": {"https://example.org/ada"}, "complaintName": {"Ombudsstelle"}, "complaintEmail": {"feedback@example.org"}, "legalRole": {"deployer"}, "useContext": {"professional"}, "outputDate": {"2026-08-03"}, "publicInterestAssessment": {"yes"}, "lawEnforcementAuthorization": {"no"}, }, "https://example.org/context/v1") if err != nil { t.Fatal(err) } if d.Author == nil || d.Author.Name != "Ada Beispiel" || d.Author.URL == "" { t.Fatalf("author not serialized: %#v", d.Author) } if d.ComplaintsContact == nil || d.ComplaintsContact.Email != "feedback@example.org" { t.Fatalf("complaints contact not serialized: %#v", d.ComplaintsContact) } if d.LegalContext == nil || d.LegalContext.Role != "deployer" || d.LegalContext.UseContext != "professional" || d.LegalContext.OutputDate != "2026-08-03" || d.LegalContext.PublicInterestAssessment != "yes" || d.LegalContext.LawEnforcementAuthorization != "no" { t.Fatalf("extended legal context not serialized: %#v", d.LegalContext) } if len(d.LegalContext.Categories) != 1 || d.LegalContext.Categories[0] != "publicInterestText" { t.Fatalf("tri-state yes should derive legal category: %#v", d.LegalContext.Categories) } } func TestComplaintsContactRequiresContactMethod(t *testing.T) { _, err := NewFromQuery(url.Values{ "preset": {"research"}, "complaintName": {"Ombudsstelle"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "complaintsContact requires email or url") { t.Fatalf("expected complaints contact validation error, got %v", err) } } func TestLegalContextValidatesRoleUseAndDate(t *testing.T) { _, err := NewFromQuery(url.Values{ "preset": {"research"}, "legalRole": {"invalid"}, "useContext": {"professional"}, "outputDate": {"2026-08-03"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "legalContext.role") { t.Fatalf("expected legal role validation error, got %v", err) } _, err = NewFromQuery(url.Values{ "preset": {"research"}, "legalRole": {"deployer"}, "useContext": {"professional"}, "outputDate": {"03.08.2026"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "legalContext.outputDate") { t.Fatalf("expected legal output date validation error, got %v", err) } _, err = NewFromQuery(url.Values{ "preset": {"research"}, "legalRole": {"deployer"}, "useContext": {"professional"}, "lawEnforcementAuthorization": {"maybe"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "legalContext.lawEnforcementAuthorization") { t.Fatalf("expected law-enforcement authorisation validation error, got %v", err) } } func TestLegalAssessmentTriStateValidation(t *testing.T) { _, err := NewFromQuery(url.Values{ "mode": {"article"}, "imageExtent": {"full"}, "deepfakeAssessment": {"maybe"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "deepfakeAssessment must be yes, no or unsure") { t.Fatalf("expected tri-state validation error, got %v", err) } _, err = NewFromQuery(url.Values{ "mode": {"article"}, "imageExtent": {"full"}, "legalContext": {"deepfake"}, "deepfakeAssessment": {"no"}, }, "https://example.org/context/v1") if err == nil || !strings.Contains(err.Error(), "conflicts with deepfake legalContext category") { t.Fatalf("expected category/assessment conflict, got %v", err) } }