package processing import ( "strings" "testing" "unicode/utf8" ) func TestFillMetaFromResult_buildsTitleAndDesc(t *testing.T) { t.Parallel() title, desc := fillMetaFromResult(StepResult{ ProcessedName: "Drill 18V", Category: "Tools", ProcessedDescription: "Cordless drill with battery pack for DIY projects.", ProcessedAttributes: map[string]any{"brand": "Bosch"}, }) if title == "" || desc == "" { t.Fatal("expected non-empty meta") } if !strings.Contains(title, "Drill 18V") { t.Fatalf("title missing name: %q", title) } if !strings.Contains(title, "Bosch") { t.Fatalf("title missing brand: %q", title) } if utf8.RuneCountInString(title) > metaTitleMaxChars { t.Fatalf("title too long: %d", utf8.RuneCountInString(title)) } if utf8.RuneCountInString(desc) > metaDescriptionMaxChars { t.Fatalf("desc too long: %d", utf8.RuneCountInString(desc)) } } func TestFillMetaFromResult_usesCategoryNameNotUniqueID(t *testing.T) { t.Parallel() title, _ := fillMetaFromResult(StepResult{ ProcessedName: "Gorenje GEC5A21WG", Category: "50", CategoryName: "Štedilniki", ProcessedAttributes: map[string]any{ "brand": "Gorenje", }, }) if strings.Contains(title, "| 50") || strings.HasSuffix(title, "50") { t.Fatalf("meta title must not use unique_id: %q", title) } if !strings.Contains(title, "Štedilniki") { t.Fatalf("meta title missing display name: %q", title) } // Digits-only Category without CategoryName must omit category from title. title2, _ := fillMetaFromResult(StepResult{ ProcessedName: "Cooker", Category: "50", }) if strings.Contains(title2, "50") { t.Fatalf("digits-only unique_id must not appear in meta: %q", title2) } } func TestTruncateMetaDescription_wordBoundary(t *testing.T) { t.Parallel() // 160+ runes with a clear word break near 155. words := strings.Repeat("word ", 40) // 200 chars with spaces out := truncateMetaDescription(words, v1MetaDescriptionMaxChars) n := utf8.RuneCountInString(out) if n > v1MetaDescriptionMaxChars { t.Fatalf("len=%d want <= %d (%q)", n, v1MetaDescriptionMaxChars, out) } if strings.HasSuffix(out, "wor") || strings.HasSuffix(out, "wo") || strings.HasSuffix(out, "w") { t.Fatalf("mid-word cut: %q", out) } if !strings.HasSuffix(out, "word") { t.Fatalf("expected last full word, got %q", out) } } func TestTruncateMetaDescription_collapsesSpace(t *testing.T) { t.Parallel() out := truncateMetaDescription(" hello \n\t world ", 155) if out != "hello world" { t.Fatalf("got %q", out) } } func TestV1PollMetaFallback_usesTemplateWhenEmpty(t *testing.T) { t.Parallel() title := "Cordless Drill" cat := "Tools" plain := strings.Repeat("quality cordless drill for home and workshop use. ", 10) synthTitle, synthDesc := fillMetaFromResult(StepResult{ Name: title, ProcessedName: title, Category: cat, Description: plain, ProcessedDescription: plain, Attributes: map[string]any{"brand": "Makita"}, }) if synthTitle == "" || synthDesc == "" { t.Fatal("expected synth meta") } if utf8.RuneCountInString(synthDesc) > v1MetaDescriptionMaxChars { t.Fatalf("poll meta_description too long: %d", utf8.RuneCountInString(synthDesc)) } // Word-safe: last rune should not be mid-word fragment from a hard cut. r := []rune(synthDesc) if len(r) == v1MetaDescriptionMaxChars { // hard-cut path only when no good space; otherwise last char is letter from a word end last := r[len(r)-1] if last == ' ' { t.Fatalf("trailing space in meta_description: %q", synthDesc) } } }