Files
2026-08-16 23:42:00 +02:00

175 lines
5.8 KiB
Go

package processing
import (
"context"
"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 TestMetaFieldsFromEnhanceObj_snakeAndCamel(t *testing.T) {
t.Parallel()
mt, md := metaFieldsFromEnhanceObj(map[string]any{
"meta_title": "Acme Widget Pro | Daily Use",
"meta_description": "Shop Acme Widget Pro for reliable everyday performance.",
})
if mt != "Acme Widget Pro | Daily Use" {
t.Fatalf("meta_title=%q", mt)
}
if !strings.Contains(md, "Acme Widget Pro") {
t.Fatalf("meta_description=%q", md)
}
mt, md = metaFieldsFromEnhanceObj(map[string]any{
"metaTitle": "<b>Camel Title</b>",
"metaDescription": "<p>Camel desc with enough characters for a real SEO snippet about the product.</p>",
})
if strings.Contains(mt, "<") || mt == "" {
t.Fatalf("camel meta_title should be plain: %q", mt)
}
if strings.Contains(md, "<") || md == "" {
t.Fatalf("camel meta_description should be plain: %q", md)
}
mt, md = metaFieldsFromEnhanceObj(map[string]any{
"meta_title": "short retail title; follow any Title formula",
})
if mt != "" || md != "" {
t.Fatalf("leakage meta must be dropped: %q %q", mt, md)
}
}
func TestRunSteps_enhancePersistsMetaFields(t *testing.T) {
e := &Engine{Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{
"name":"Acme Widget Pro",
"description":"<h2>Acme Widget Pro</h2><p>Durable widget for everyday use.</p>",
"meta_title":"Acme Widget Pro | Durable Daily Use",
"meta_description":"Shop Acme Widget Pro for reliable everyday performance. Clear specs and fast delivery."
}`, TotalTokens: 12}, nil
}}}
out, err := e.RunSteps(context.Background(), "c1", ProductInput{
Mapped: map[string]any{"name": "Raw", "description": "old", "category": "tools"},
Language: "en",
}, "full", nil, StepPolicy{AllowAI: true})
if err != nil {
t.Fatal(err)
}
if out.MetaTitle != "Acme Widget Pro | Durable Daily Use" {
t.Fatalf("MetaTitle=%q", out.MetaTitle)
}
if !strings.Contains(out.MetaDescription, "Shop Acme Widget Pro") {
t.Fatalf("MetaDescription=%q", out.MetaDescription)
}
if !strings.Contains(out.ProcessedDescription, "<h2>") {
t.Fatalf("description should stay HTML: %q", out.ProcessedDescription)
}
if strings.Contains(out.MetaDescription, "<") {
t.Fatalf("meta_description must be plain: %q", out.MetaDescription)
}
}
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)
}
}
}