Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
package processing
import (
"context"
"fmt"
"strings"
"testing"
"github.com/descrybe/descrybe-v2/apps/api/internal/eprel"
)
type stubCompleter struct {
fn func(system, user string) (Completion, error)
}
func (s stubCompleter) Complete(_ context.Context, system, user string) (Completion, error) {
return s.fn(system, user)
}
func TestResolveSteps(t *testing.T) {
cases := map[string][]string{
"full": {StepNormalize, StepParseSpecs, StepFillFields, StepEPREL, StepAIEnhance},
"enhance_only": {StepNormalize, StepAIEnhance},
"attributes_only": {StepNormalize, StepParseSpecs, StepFillFields},
"eprel_only": {StepNormalize, StepEPREL},
"normalize_only": {StepNormalize},
}
for in, want := range cases {
got := resolveSteps(in)
if strings.Join(got, ",") != strings.Join(want, ",") {
t.Fatalf("%s: got %v want %v", in, got, want)
}
}
}
func TestRunSteps_fullMock(t *testing.T) {
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{"name":"Red Runner","description":"A fine shoe."}`, TotalTokens: 7, Raw: map[string]any{"ok": true}}, nil
}},
Vector: NoopVectorCategorizer{},
EPREL: nil,
}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
GTIN: "123", Name: "Runner", Description: "shoe",
Mapped: map[string]any{"brand": "Acme"},
}, "full", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
if err != nil {
t.Fatal(err)
}
if out.ProcessedName != "Red Runner" {
t.Fatalf("name=%q", out.ProcessedName)
}
if out.TotalTokens < 1 {
t.Fatalf("tokens=%d", out.TotalTokens)
}
}
func TestRunSteps_enhanceOnly(t *testing.T) {
calls := 0
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
calls++
return Completion{Text: `{"name":"N","description":"D"}`, TotalTokens: 1}, nil
}},
Vector: NoopVectorCategorizer{},
}
_, err := e.RunSteps(context.Background(), "co", ProductInput{Name: "x"}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
if err != nil {
t.Fatal(err)
}
if calls != 1 {
t.Fatalf("calls=%d", calls)
}
}
func TestRunSteps_enhanceOnly_keepsPriorCategoryWhenMappedEmpty(t *testing.T) {
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{"name":"N","description":"D"}`, TotalTokens: 1}, nil
}},
Vector: NoopVectorCategorizer{},
}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
GTIN: "5905575903198",
Name: "Adler",
Mapped: map[string]any{"name": "Adler", "description": "radiator"},
PriorCategory: "Radiators",
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
if err != nil {
t.Fatal(err)
}
if out.Category != "Radiators" {
t.Fatalf("category=%q want Radiators", out.Category)
}
if src, _ := out.FieldSources["category"].(string); src != "prior_processed" {
t.Fatalf("field_sources.category=%v", out.FieldSources["category"])
}
}
func TestHeuristicCompleter_usesNameNotCategoryLine(t *testing.T) {
h := HeuristicCompleter{}
user := ProductEnhanceUser("120", "Adler LED radiator", "Bathroom heater", map[string]any{"brand": "ADLER"})
comp, err := h.Complete(context.Background(), `Return JSON with "name" and "description".`, user)
if err != nil {
t.Fatal(err)
}
obj, err := ParseJSONObject(comp.Text)
if err != nil {
t.Fatal(err)
}
name := SanitizeOutput(fmt.Sprint(obj["name"]))
if isPromptLabelTitle(name) || strings.HasPrefix(strings.ToLower(name), "category:") {
t.Fatalf("heuristic echoed prompt label as title: %q", name)
}
if !strings.Contains(strings.ToLower(name), "adler") {
t.Fatalf("name=%q want Adler from Name: line", name)
}
}
func TestEnhance_rejectsPromptLabelTitle(t *testing.T) {
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{"name":"Category:","description":"ok"}`, TotalTokens: 2}, nil
}},
Vector: NoopVectorCategorizer{},
}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
GTIN: "1",
Name: "Good Title",
Mapped: map[string]any{"name": "Good Title", "description": "d", "category": "demo-electronics"},
}, "enhance_only", nil, StepPolicy{AllowAI: true})
if err != nil {
t.Fatal(err)
}
if out.ProcessedName != "Good Title" {
t.Fatalf("ProcessedName=%q want Good Title (reject Category:)", out.ProcessedName)
}
if out.Name == "Category:" || isPromptLabelTitle(out.Name) {
t.Fatalf("Name polluted: %q", out.Name)
}
if out.Category != "demo-electronics" {
t.Fatalf("category=%q", out.Category)
}
}
func TestRunSteps_enhanceOnly_keepsMappedTitleWhenAIReturnsCategoryLabel(t *testing.T) {
// A1 Elkotex Adler shape: title in mapped_data, not name.
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{"name":"Category:","description":"Category: 120"}`, TotalTokens: 2}, nil
}},
Vector: NoopVectorCategorizer{},
}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
GTIN: "5905575903198",
Mapped: map[string]any{
"title": "Adler LED kopalniski radiator lestev 600W AD7824",
"description": "Bathroom radiator",
"category": "120",
},
PriorCategory: "120",
PriorProcessedName: "Category:",
}, "enhance_only", nil, StepPolicy{AllowAI: true})
if err != nil {
t.Fatal(err)
}
if isPromptLabelTitle(out.ProcessedName) || out.ProcessedName == "Category:" {
t.Fatalf("ProcessedName=%q", out.ProcessedName)
}
if !strings.Contains(out.ProcessedName, "Adler") {
t.Fatalf("ProcessedName=%q want mapped title", out.ProcessedName)
}
if out.Category != "120" {
t.Fatalf("category=%q", out.Category)
}
}
func TestPreferredProductTitle_skipsPromptLabels(t *testing.T) {
got := preferredProductTitle("99", "Category:", "Category: 120", "Name:", "Real Product")
if got != "Real Product" {
t.Fatalf("got %q", got)
}
got = preferredProductTitle("99", "Category:", "")
if got != "Product 99" {
t.Fatalf("fallback got %q", got)
}
}
func TestIsPromptLabelTitle(t *testing.T) {
cases := map[string]bool{
"Category:": true,
"category: 120": true,
"Category : 46": true,
"Name:": true,
"Desc:": true,
"Adler LED": false,
"": false,
}
for in, want := range cases {
if got := isPromptLabelTitle(in); got != want {
t.Fatalf("%q: got %v want %v", in, got, want)
}
}
}
func TestRunSteps_full_mappedCategoryWinsOverPrior(t *testing.T) {
e := &Engine{
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
return Completion{Text: `{"name":"N","description":"D"}`, TotalTokens: 1}, nil
}},
Vector: NoopVectorCategorizer{},
}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
GTIN: "6970995789942",
Mapped: map[string]any{
"name": "Roborock",
"description": "vacuum",
"category": "Robot Vacuums",
},
PriorCategory: "Old Category",
}, "full", nil, StepPolicy{AllowAI: true, AllowEPREL: false})
if err != nil {
t.Fatal(err)
}
if out.Category != "Robot Vacuums" {
t.Fatalf("category=%q want Robot Vacuums", out.Category)
}
}
func TestRunSteps_normalizeOnly_keepsPriorWhenMappedMissing(t *testing.T) {
e := &Engine{Vector: NoopVectorCategorizer{}}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
Mapped: map[string]any{"name": "x"},
PriorCategory: "FromProcessed",
}, "normalize_only", nil, StepPolicy{})
if err != nil {
t.Fatal(err)
}
if out.Category != "FromProcessed" {
t.Fatalf("category=%q", out.Category)
}
}
func TestRunSteps_skipsEPRELWithoutEntitlement(t *testing.T) {
st := &stubEprel{enabled: true, data: &eprel.Data{ID: "1", EnergyClass: "A"}}
e := &Engine{EPREL: st}
out, err := e.RunSteps(context.Background(), "co", ProductInput{
Raw: map[string]any{"EPRELID": "1"},
}, "eprel_only", nil, StepPolicy{AllowEPREL: false})
if err != nil {
t.Fatal(err)
}
if st.calls != 0 {
t.Fatal("EPREL should not run without entitlement")
}
if len(out.Notes) == 0 {
t.Fatal("expected skip note")
}
}