package extract_test import ( "context" "strings" "testing" "github.com/netcell-it/deklarix/internal/extract" "github.com/netcell-it/deklarix/internal/rules" ) func TestExtractRejectsEmptyCaption(t *testing.T) { e := extract.NewEngine() if _, err := e.Extract(context.Background(), extract.Input{Platform: "instagram", Consideration: "bezahlt"}); err == nil { t.Fatal("expected error for empty caption, got nil") } } func TestExtractRejectsMissingConsideration(t *testing.T) { e := extract.NewEngine() if _, err := e.Extract(context.Background(), extract.Input{Platform: "instagram", Caption: "irgendein Text"}); err == nil { t.Fatal("expected error for missing consideration, got nil") } } func TestExtractRejectsInvalidConsideration(t *testing.T) { e := extract.NewEngine() _, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Caption: "Text", Consideration: "vielleicht", }) if err == nil { t.Fatal("expected error for an out-of-enum consideration value, got nil") } } func TestExtractDetectsDisclosureKeyword(t *testing.T) { e := extract.NewEngine() result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Consideration: "bezahlt", Caption: "Werbung: Schaut euch dieses tolle Produkt an!", }) if err != nil { t.Fatalf("Extract: %v", err) } if !result.Facts.DisclosurePresent { t.Error("expected DisclosurePresent = true") } if result.Facts.DisclosureWording != "Werbung" { t.Errorf("DisclosureWording = %q, want Werbung (original casing preserved)", result.Facts.DisclosureWording) } if !result.Facts.DisclosureBeforeCut { t.Error("expected DisclosureBeforeCut = true (Werbung is at index 0)") } } func TestExtractIsCaseInsensitive(t *testing.T) { e := extract.NewEngine() result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Consideration: "bezahlt", Caption: "WERBUNG fuer ein Produkt", }) if err != nil { t.Fatalf("Extract: %v", err) } if !result.Facts.DisclosurePresent { t.Fatal("expected case-insensitive match to find the keyword") } } func TestExtractDetectsDisclosureAfterTruncationCut(t *testing.T) { e := extract.NewEngine() // 130 Fuellzeichen vor "Werbung" -> jenseits der Instagram-Schwelle (125). padding := strings.Repeat("x", 130) result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Consideration: "bezahlt", Caption: padding + " Werbung", }) if err != nil { t.Fatalf("Extract: %v", err) } if !result.Facts.DisclosurePresent { t.Fatal("expected the keyword to still be found even though it's late in the caption") } if result.Facts.DisclosureBeforeCut { t.Error("expected DisclosureBeforeCut = false when the keyword appears after the platform's truncation threshold") } } func TestExtractNoDisclosureFound(t *testing.T) { e := extract.NewEngine() result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Consideration: "keine", Caption: "Ein ganz normaler Tag im Park.", }) if err != nil { t.Fatalf("Extract: %v", err) } if result.Facts.DisclosurePresent { t.Error("expected DisclosurePresent = false when no keyword is present") } if result.Facts.DisclosureBeforeCut { t.Error("expected DisclosureBeforeCut = false when there is no disclosure at all") } } func TestExtractPassesThroughUnclearConsideration(t *testing.T) { e := extract.NewEngine() result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Consideration: "unklar", Caption: "Text ohne klare Angabe", }) if err != nil { t.Fatalf("Extract: %v", err) } if result.Facts.Consideration != rules.ConsiderationUnclear { t.Fatalf("Consideration = %q, want unklar", result.Facts.Consideration) } } func TestExtractDifferentPlatformThresholds(t *testing.T) { e := extract.NewEngine() // 140 Fuellzeichen: unter der TikTok-Schwelle (150), aber ueber der // Instagram-Schwelle (125) -> gleiche Caption, unterschiedliches Ergebnis. padding := strings.Repeat("x", 140) caption := padding + " Werbung" igResult, err := e.Extract(context.Background(), extract.Input{Platform: "instagram", Consideration: "bezahlt", Caption: caption}) if err != nil { t.Fatalf("Extract (instagram): %v", err) } ttResult, err := e.Extract(context.Background(), extract.Input{Platform: "tiktok", Consideration: "bezahlt", Caption: caption}) if err != nil { t.Fatalf("Extract (tiktok): %v", err) } if igResult.Facts.DisclosureBeforeCut { t.Error("expected DisclosureBeforeCut = false for instagram at this length") } if !ttResult.Facts.DisclosureBeforeCut { t.Error("expected DisclosureBeforeCut = true for tiktok at this length") } } func TestExtractRawJSONRoundTripsThroughParsePayload(t *testing.T) { e := extract.NewEngine() result, err := e.Extract(context.Background(), extract.Input{ Platform: "instagram", Jurisdiction: "DE", Consideration: "sachbezug", Caption: "Anzeige: dieses Produkt wurde mir geschenkt", }) if err != nil { t.Fatalf("Extract: %v", err) } facts, err := extract.ParsePayload(result.RawJSON, "instagram", "DE") if err != nil { t.Fatalf("ParsePayload: %v", err) } if facts != result.Facts { t.Fatalf("ParsePayload(Extract().RawJSON) = %+v, want %+v", facts, result.Facts) } } func TestParsePayloadRejectsInvalidConsideration(t *testing.T) { payload := []byte(`{"gegenleistung":"vielleicht","kennzeichnung_vorhanden":false,"kennzeichnung_wortlaut":"","kennzeichnung_vor_kuerzung":false}`) if _, err := extract.ParsePayload(payload, "tiktok", "DE"); err == nil { t.Fatal("expected error for out-of-enum stored payload, got nil") } }