Files
descrybe/apps/api/internal/processing/v1_process_item_test.go
T

320 lines
12 KiB
Go
Raw Normal View History

2026-08-16 16:57:36 +02:00
package processing
import (
"fmt"
"strings"
"testing"
)
func TestScoreV1ProcessCompletedItem_processedStructure(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "123",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": "Sony Headphones",
"name": "Sony Headphones",
"description": "Sony Headphones deliver clear audio with noise cancelling for daily commuting.",
"meta_title": "Sony | Sony Headphones",
"meta_description": "Sony Headphones deliver clear audio with noise cancelling for daily commuting.",
"category": "48",
"category_name": "Slušalke",
"attributes": map[string]any{"brand": "Sony", "color": "Black"},
"main_image": "https://example.com/a.jpg",
"more_images": []string{"https://example.com/b.jpg"},
"eprel": nil,
}
sc := ScoreV1ProcessCompletedItem(item, ScoreV1ProcessItemOptions{MappedCategory: "48"})
if !sc.OK {
t.Fatalf("expected OK, flags=%v score=%d", sc.FailFlags, sc.Score)
}
if sc.Score < 90 {
t.Fatalf("score=%d want >= 90", sc.Score)
}
}
func TestScoreV1ProcessCompletedItem_flagsGaps(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "999",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": "Mount",
"description": nil,
"meta_title": nil,
"meta_description": nil,
"category": nil,
"category_name": nil,
"attributes": map[string]any{
"brand": "X",
"eprel_id": "1",
":": "true",
},
"eprel": "not-an-object",
}
sc := ScoreV1ProcessCompletedItem(item, ScoreV1ProcessItemOptions{MappedCategory: "28"})
if sc.OK {
t.Fatal("expected failures")
}
joined := strings.Join(sc.FailFlags, ",")
for _, want := range []string{
"missing_name",
"description_empty_with_title",
"meta_title_missing",
"meta_description_missing",
"category_missing_though_mapped",
"eprel_invalid_shape",
} {
if !strings.Contains(joined, want) {
t.Fatalf("missing flag %q in %v", want, sc.FailFlags)
}
}
}
func TestEnforceV1ProcessCompletedItem_fillsDescriptionMetaSanitizes(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "1",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": "Fiksni stenski TV nosilec 32''-55'' MANHATTAN",
"description": "Fiksni stenski TV nosilec 32''-55'' MANHATTAN from Ostalo. Ready for retail listing.",
"meta_title": nil,
"meta_description": nil,
"category": "28",
"category_name": "Nosilci za TV",
"attributes": map[string]any{
"brand": "Ostalo",
"product_model": "460934",
"eprel_label": "A",
"name": "should-drop",
":": "true",
},
"eprel": nil,
}
out := EnforceV1ProcessCompletedItem(item, "sl", nil)
desc, _ := out["description"].(string)
if desc == "" {
t.Fatal("expected nonempty description")
}
if descriptionEchoesTitle(desc, fmt.Sprint(out["title"])) {
t.Fatalf("EnforceV1 must replace weak/echo desc, got title-echo: %q", desc)
}
if containsWeakFillerPhrase(desc) {
t.Fatalf("filler left in description: %q", desc)
}
if strings.TrimSpace(fmt.Sprint(out["meta_title"])) == "" || out["meta_title"] == nil {
t.Fatalf("meta_title empty: %v", out["meta_title"])
}
if strings.TrimSpace(fmt.Sprint(out["meta_description"])) == "" || out["meta_description"] == nil {
t.Fatalf("meta_description empty: %v", out["meta_description"])
}
if containsWeakFillerPhrase(fmt.Sprint(out["meta_description"])) {
t.Fatalf("meta_description still filler: %v", out["meta_description"])
}
attrs, ok := out["attributes"].(map[string]any)
if !ok {
t.Fatalf("attributes type=%T", out["attributes"])
}
if _, bad := attrs["eprel_label"]; bad {
t.Fatalf("eprel_* should be stripped: %v", attrs)
}
if _, bad := attrs[":"]; bad {
t.Fatalf("invalid key kept: %v", attrs)
}
if _, bad := attrs["name"]; bad {
t.Fatalf("reserved name kept: %v", attrs)
}
if out["name"] != out["title"] {
t.Fatalf("name should mirror title: name=%v title=%v", out["name"], out["title"])
}
sc := ScoreV1ProcessCompletedItem(out, ScoreV1ProcessItemOptions{MappedCategory: "28"})
if !sc.OK {
t.Fatalf("enforced item should score OK, flags=%v", sc.FailFlags)
}
}
func TestEnforceV1ProcessCompletedItem_skipsTerminalStatuses(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{"ean": "1", "status": "not_found", "error": "missing"}
out := EnforceV1ProcessCompletedItem(item, "", nil)
if _, ok := out["title"]; ok {
t.Fatalf("should not invent fields for not_found: %v", out)
}
}
func TestEnforceV1ProcessCompletedItem_categoryFromCallerPreserved(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "2",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": "Cooker",
"description": "",
"category": "50",
"category_name": "Štedilniki",
"attributes": map[string]any{"brand": "Vox"},
"eprel": nil,
}
out := EnforceV1ProcessCompletedItem(item, "", nil)
if out["category"] != "50" {
t.Fatalf("category=%v", out["category"])
}
if out["category_name"] != "Štedilniki" {
t.Fatalf("category_name=%v", out["category_name"])
}
desc, _ := out["description"].(string)
if desc == "" {
t.Fatal("expected synthesized description when title set")
}
if descriptionEchoesTitle(desc, "Cooker") {
t.Fatalf("synthesized description must not equal title: %q", desc)
}
sc := ScoreV1ProcessCompletedItem(out, ScoreV1ProcessItemOptions{MappedCategory: "50"})
if !sc.OK {
t.Fatalf("flags=%v", sc.FailFlags)
}
}
func TestEnforceV1ProcessCompletedItem_replacesTitleEchoDescription(t *testing.T) {
t.Parallel()
title := "VOX EBR700"
item := V1ProcessJobItem{
"ean": "8806095210711",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": title,
"description": title,
"category": "50",
"category_name": "Štedilniki",
"attributes": map[string]any{"brand": "VOX"},
"eprel": nil,
}
out := EnforceV1ProcessCompletedItem(item, "sl", nil)
desc, _ := out["description"].(string)
if desc == "" {
t.Fatal("expected synthesized description")
}
if descriptionEchoesTitle(desc, title) {
t.Fatalf("must not leave title-echo description: %q", desc)
}
if !strings.Contains(desc, "Štedilniki") && !strings.Contains(desc, "VOX") {
t.Fatalf("expected factual synth with category/brand: %q", desc)
}
}
func TestEnforceV1ProcessCompletedItem_refreshesPoisonedMetaTitle(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "8806095210711",
"status": "processed",
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"processed_product_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"raw_product_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"title": "VOX EBR700",
"description": "VOX EBR700 is a Štedilniki product from VOX.",
"meta_title": "VOX EBR700 | 50",
"meta_description": "Ready for retail listing.",
"category": "50",
"category_name": "Štedilniki",
"attributes": map[string]any{
"brand": "VOX",
"zavora": "junk",
},
"eprel": nil,
}
out := EnforceV1ProcessCompletedItem(item, "sl", nil)
mt := fmt.Sprint(out["meta_title"])
if isPoisonedMetaTitle(mt) || strings.Contains(mt, "| 50") {
t.Fatalf("poisoned meta_title preserved: %q", mt)
}
if !strings.Contains(mt, "Štedilniki") {
t.Fatalf("expected CategoryName in meta_title: %q", mt)
}
md := fmt.Sprint(out["meta_description"])
if md == "" || md == "<nil>" || out["meta_description"] == nil {
t.Fatalf("meta_description empty after poisoned title refresh: %v", out["meta_description"])
}
if containsWeakFillerPhrase(md) || isWeakPriorEnhanceDescription(md, "VOX EBR700") {
t.Fatalf("stub meta_description preserved with poisoned title: %q", md)
}
attrs, ok := out["attributes"].(map[string]any)
if !ok {
t.Fatalf("attributes type=%T", out["attributes"])
}
if _, bad := attrs["zavora"]; bad {
t.Fatalf("feed junk zavora leaked: %v", attrs)
}
if _, ok := attrs["brand"]; !ok {
t.Fatalf("brand should remain: %v", attrs)
}
}
func TestIsPoisonedMetaTitle(t *testing.T) {
t.Parallel()
if !isPoisonedMetaTitle("VOX EBR700 | 50") {
t.Fatal("expected poisoned")
}
if !isPoisonedMetaTitle("Name | 7") {
t.Fatal("expected poisoned with extra spaces")
}
if isPoisonedMetaTitle("VOX | Štedilniki") {
t.Fatal("display name suffix must not count as poisoned")
}
if isPoisonedMetaTitle("") {
t.Fatal("empty not poisoned")
}
if !isPoisonedMetaTitle("Sony | short retail title; follow any Title formula constr…") {
t.Fatal("expected prompt-leakage meta_title poisoned")
}
if !isPoisonedMetaTitle("ANKER | short retail title; follow any Title formula constraints that follow") {
t.Fatal("expected Title formula leakage poisoned")
}
}
func TestEnforceV1ProcessCompletedItem_refreshesPromptLeakageMeta(t *testing.T) {
t.Parallel()
item := V1ProcessJobItem{
"ean": "4548736132597",
"status": "processed",
"id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"processed_product_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"raw_product_id": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
"title": "SONY BT slušalke WH1000XM5S sive",
"description": "SONY BT slušalke WH1000XM5S sive je izdelek v kategoriji Slušalke znamke Sony.",
"meta_title": "Sony | short retail title; follow any Title formula constr…",
"meta_description": "prefer 1-3 factual paragraphs as ONE string; limited HTML () is allowed if needed",
"category": "48",
"category_name": "Slušalke",
"attributes": map[string]any{
"brand": "Sony",
},
"eprel": nil,
}
out := EnforceV1ProcessCompletedItem(item, "sl", nil)
mt := fmt.Sprint(out["meta_title"])
if isPoisonedMetaTitle(mt) || strings.Contains(strings.ToLower(mt), "short retail title") {
t.Fatalf("prompt-leakage meta_title preserved: %q", mt)
}
if !strings.Contains(mt, "Slušalke") && !strings.Contains(mt, "SONY") {
t.Fatalf("expected synth meta_title with product/category: %q", mt)
}
md := fmt.Sprint(out["meta_description"])
if md == "" || md == "<nil>" || out["meta_description"] == nil {
t.Fatalf("meta_description empty after leakage refresh: %v", out["meta_description"])
}
if isPromptLeakageTitle(md) || strings.Contains(strings.ToLower(md), "prefer 1-3") {
t.Fatalf("prompt-leakage meta_description preserved: %q", md)
}
}