175 lines
5.8 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|