182 lines
6.7 KiB
Go
182 lines
6.7 KiB
Go
package declaration
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
"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)
|
|
}
|
|
}
|
|
|
|
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"}}}
|
|
d.Presentation = &Presentation{Title: "Allowed since 1.1"}
|
|
if err := Validate(d); err != nil {
|
|
t.Fatalf("1.1 declaration should remain valid: %v", err)
|
|
}
|
|
d.RegulatoryContext = &RegulatoryContext{Framework: "EU-AI-Act-Article-50", Deepfake: true}
|
|
if Validate(d) == nil {
|
|
t.Fatal("expected regulatoryContext to require schema 1.2")
|
|
}
|
|
d.SchemaVersion = LegacySchemaVersion10
|
|
d.RegulatoryContext = nil
|
|
if Validate(d) == nil {
|
|
t.Fatal("expected presentation to require schema 1.1 or newer")
|
|
}
|
|
}
|
|
|
|
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 != "editorial" {
|
|
t.Fatalf("AI-assisted component review=%q, want editorial default", 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 TestArticle50Assessment(t *testing.T) {
|
|
base := Declaration{Context: "https://example.org/context/v1", Type: "AIUsageDeclaration", SchemaVersion: SchemaVersion, Language: "de", Assurance: "selfDeclared", Components: map[string]Component{"text": {AIExtent: "partial", HumanReview: "expert"}}}
|
|
base.RegulatoryContext = &RegulatoryContext{Framework: "EU-AI-Act-Article-50", PublicInterestText: true}
|
|
assessment := AssessArticle50(base)
|
|
if assessment.Code != "public_interest_text_disclosure_relevant" || !assessment.Applicable {
|
|
t.Fatalf("unexpected assessment: %#v", assessment)
|
|
}
|
|
base.RegulatoryContext.SubstantialHumanReview = true
|
|
base.RegulatoryContext.EditorialResponsibilityConfirmed = true
|
|
assessment = AssessArticle50(base)
|
|
if assessment.Code != "public_interest_text_possible_exemption" {
|
|
t.Fatalf("unexpected exemption assessment: %#v", assessment)
|
|
}
|
|
base.RegulatoryContext.Deepfake = true
|
|
assessment = AssessArticle50(base)
|
|
if assessment.Code != "multiple_article50_contexts" || len(assessment.Findings) != 2 {
|
|
t.Fatalf("combined article context should preserve both findings: %#v", assessment)
|
|
}
|
|
}
|
|
|
|
func TestRegulatoryContextFromQuery(t *testing.T) {
|
|
d, err := NewFromQuery(url.Values{
|
|
"preset": {"research"}, "lang": {"de"}, "publicInterestText": {"true"},
|
|
"substantialHumanReview": {"1"}, "editorialResponsibilityConfirmed": {"yes"},
|
|
}, "https://example.org/context/v1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if d.RegulatoryContext == nil || !d.RegulatoryContext.PublicInterestText || !d.RegulatoryContext.SubstantialHumanReview || !d.RegulatoryContext.EditorialResponsibilityConfirmed {
|
|
t.Fatalf("unexpected regulatory context: %#v", d.RegulatoryContext)
|
|
}
|
|
}
|
|
|
|
func TestNoAIPresetDoesNotClaimHumanAIReview(t *testing.T) {
|
|
values := url.Values{"preset": {"no-ai"}, "lang": {"de"}}
|
|
d, err := NewFromQuery(values, "https://example.org/context/v1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := d.Components["text"].HumanReview; got != "none" {
|
|
t.Fatalf("no-ai review = %q, want none", got)
|
|
}
|
|
}
|
|
|
|
func TestArticle50ArtisticDeepfakeWarning(t *testing.T) {
|
|
d := Declaration{
|
|
RegulatoryContext: &RegulatoryContext{Deepfake: true, ArtisticCreativeSatiricalFictional: true},
|
|
}
|
|
assessment := AssessArticle50(d)
|
|
found := false
|
|
for _, warning := range assessment.Warnings {
|
|
if warning == "artistic_context_disclosure_manner" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected artistic context warning, got %#v", assessment.Warnings)
|
|
}
|
|
}
|