package seo import ( "fmt" "testing" ) func TestAnalyze_missingMetaAndImage(t *testing.T) { products := []ProductInput{ { ID: "p1", Name: "Acme UltraWidget Pro 3000 Stainless", Category: "Widgets", ProcessedAttrs: map[string]any{"brand": "Acme"}, }, } recs := Analyze(products, nil) types := map[string]bool{} for _, r := range recs { types[r.Type] = true if r.EntityID != "p1" && r.Type != TypeDuplicateTitle { t.Fatalf("unexpected entity %s", r.EntityID) } } for _, want := range []string{TypeMissingMetaTitle, TypeMissingMetaDescription, TypeMissingImage} { if !types[want] { t.Fatalf("expected type %s", want) } } } func TestAnalyze_duplicateAndThin(t *testing.T) { products := []ProductInput{ {ID: "a", Name: "Short", MetaTitle: "x", MetaDescription: "y"}, {ID: "b", Name: "Short", MetaTitle: "x", MetaDescription: "y"}, } recs := Analyze(products, nil) var thin, dup int for _, r := range recs { switch r.Type { case TypeThinTitle: thin++ case TypeDuplicateTitle: dup++ } } if thin < 2 { t.Fatalf("expected thin titles, got %d", thin) } if dup < 2 { t.Fatalf("expected duplicate titles, got %d", dup) } } func TestAnalyze_duplicatePeerAndRecCaps(t *testing.T) { n := maxDupRecsPerTitle + maxPeerIDsPerDup + 10 products := make([]ProductInput, 0, n) for i := 0; i < n; i++ { products = append(products, ProductInput{ ID: fmt.Sprintf("p%d", i), Name: "Shared Title Product", MetaTitle: "mt", MetaDescription: "md", }) } recs := Analyze(products, nil) var dup int var peerLen int for _, r := range recs { if r.Type != TypeDuplicateTitle { continue } dup++ peers, _ := r.Meta["peer_ids"].([]string) if peerLen == 0 { peerLen = len(peers) } if len(peers) > maxPeerIDsPerDup { t.Fatalf("peer_ids len=%d want <=%d", len(peers), maxPeerIDsPerDup) } count, _ := r.Meta["duplicate_count"].(int) if count != n { t.Fatalf("duplicate_count=%d want %d", count, n) } } if dup != maxDupRecsPerTitle { t.Fatalf("duplicate recs=%d want %d", dup, maxDupRecsPerTitle) } if peerLen != maxPeerIDsPerDup { t.Fatalf("peer_ids len=%d want %d", peerLen, maxPeerIDsPerDup) } } func TestAnalyze_categoryMeta(t *testing.T) { cats := []CategoryInput{ {ID: "c1", UniqueID: "cat-1", Name: "AB", DescriptionTemplate: map[string]any{}}, {ID: "c2", UniqueID: "cat-2", Name: "Appliances", DescriptionTemplate: map[string]any{ "metaTitle": "t", "metaDescription": "d", }}, } recs := Analyze(nil, cats) var missing, thin int for _, r := range recs { if r.Type == TypeCategoryMissingMeta { missing++ if r.EntityID != "c1" { t.Fatalf("wrong category for missing meta: %s", r.EntityID) } } if r.Type == TypeThinCategoryName { thin++ } } if missing != 1 { t.Fatalf("expected 1 missing meta formula, got %d", missing) } if thin != 1 { t.Fatalf("expected 1 thin category name, got %d", thin) } } func TestFillMetaTemplate(t *testing.T) { title, desc := FillMetaTemplate(ProductInput{ Name: "Drill 18V", Category: "Tools", Description: "Cordless drill with battery pack for DIY projects.", Attributes: map[string]any{"brand": "Bosch"}, }) if title == "" || desc == "" { t.Fatal("expected non-empty meta") } if runeLen(title) > MetaTitleMaxChars { t.Fatalf("title too long: %d", runeLen(title)) } if runeLen(desc) > MetaDescriptionMaxChars { t.Fatalf("desc too long: %d", runeLen(desc)) } } func TestBuildChecklist_score(t *testing.T) { products := []ProductInput{ {ID: "1", Name: "Good Product Title Here", MetaTitle: "mt", MetaDescription: "md", MappedData: map[string]any{"image_url": "https://example.com/a.jpg"}, Category: "Good Product Title Here"}, } recs := Analyze(products, nil) checklist, overall := BuildChecklist(recs, 1, 0) if overall <= 0 { t.Fatalf("expected positive overall, got %v", overall) } if len(checklist) != len(AllTypes()) { t.Fatalf("checklist len %d want %d", len(checklist), len(AllTypes())) } } func TestAllTypesStable(t *testing.T) { types := AllTypes() if len(types) != 8 { t.Fatalf("want 8 types, got %d", len(types)) } }