fix
This commit is contained in:
@@ -97,7 +97,7 @@ func TestRunSteps_skipsEnhanceWhenInputHashUnchanged(t *testing.T) {
|
||||
t.Fatalf("preferKnownProviderMode(unknown, internal)=%q", got)
|
||||
}
|
||||
joined := strings.Join(out.Notes, ";")
|
||||
if !strings.Contains(joined, "unchanged") {
|
||||
if !strings.Contains(joined, "ai_enhance_unchanged") {
|
||||
t.Fatalf("notes=%v", out.Notes)
|
||||
}
|
||||
}
|
||||
@@ -279,6 +279,195 @@ func TestRunSteps_synthesizedDescDoesNotPersistEnhanceHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_formulaIgnoredByLLM_retriesThenSynthesizes(t *testing.T) {
|
||||
calls := 0
|
||||
var sawFormulaRetry bool
|
||||
e := &Engine{
|
||||
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
|
||||
calls++
|
||||
if strings.Contains(user, "INVALID DESCRIPTION") {
|
||||
sawFormulaRetry = true
|
||||
}
|
||||
// Non-weak short prose that ignores multi-section HTML formula.
|
||||
return Completion{
|
||||
Text: `{"name":"GIGABYTE GS27QC","description":"GIGABYTE GS27QC is a gaming monitor with a 61 cm curved panel for immersive play."}`,
|
||||
TotalTokens: 4,
|
||||
}, nil
|
||||
}},
|
||||
Vector: NoopVectorCategorizer{},
|
||||
}
|
||||
descTpl := map[string]any{
|
||||
"sections": []any{
|
||||
map[string]any{"type": "h1", "instructions": "Heading"},
|
||||
map[string]any{"type": "p", "instructions": "Body"},
|
||||
map[string]any{"type": "ul", "instructions": "Specs"},
|
||||
},
|
||||
}
|
||||
out, err := e.RunSteps(context.Background(), "co", ProductInput{
|
||||
Name: "GIGABYTE GS27QC",
|
||||
Mapped: map[string]any{"name": "GIGABYTE GS27QC", "category": "7", "brand": "GIGABYTE", "width": "61 cm"},
|
||||
CategoryFormulasByKey: map[string]CategoryFormulas{
|
||||
"7": {DescriptionTemplate: descTpl},
|
||||
},
|
||||
Language: "en",
|
||||
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls < 2 {
|
||||
t.Fatalf("expected formula retry after ignore, calls=%d", calls)
|
||||
}
|
||||
if !sawFormulaRetry {
|
||||
t.Fatal("expected INVALID DESCRIPTION retry user suffix")
|
||||
}
|
||||
for _, tag := range []string{"<h1>", "<p>", "<ul>"} {
|
||||
if !strings.Contains(out.ProcessedDescription, tag) {
|
||||
t.Fatalf("expected formula HTML with %s, got %q", tag, out.ProcessedDescription)
|
||||
}
|
||||
}
|
||||
if h, _ := out.FieldSources[FieldEnhanceInputHash].(string); h != "" {
|
||||
t.Fatalf("formula synthesize must not persist hash, got %q", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_formulaHTMLFromLLM_persistsHash(t *testing.T) {
|
||||
e := &Engine{
|
||||
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
|
||||
return Completion{
|
||||
Text: `{"name":"GIGABYTE GS27QC","description":"<h1>GIGABYTE GS27QC</h1><p>Curved gaming monitor.</p><ul><li>width 61 cm</li></ul>"}`,
|
||||
TotalTokens: 6,
|
||||
}, nil
|
||||
}},
|
||||
Vector: NoopVectorCategorizer{},
|
||||
}
|
||||
descTpl := map[string]any{
|
||||
"sections": []any{
|
||||
map[string]any{"type": "h1", "instructions": "Heading"},
|
||||
map[string]any{"type": "p", "instructions": "Body"},
|
||||
map[string]any{"type": "ul", "instructions": "Specs"},
|
||||
},
|
||||
}
|
||||
out, err := e.RunSteps(context.Background(), "co", ProductInput{
|
||||
Name: "GIGABYTE GS27QC",
|
||||
Mapped: map[string]any{"name": "GIGABYTE GS27QC", "category": "7"},
|
||||
DescriptionTemplate: descTpl,
|
||||
Language: "en",
|
||||
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(out.ProcessedDescription, "<ul>") {
|
||||
t.Fatalf("expected LLM HTML kept, got %q", out.ProcessedDescription)
|
||||
}
|
||||
if h, _ := out.FieldSources[FieldEnhanceInputHash].(string); h == "" {
|
||||
t.Fatal("compliant formula HTML should persist enhance hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_synthPriorHashDoesNotSkipReprocess(t *testing.T) {
|
||||
calls := 0
|
||||
e := &Engine{
|
||||
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
|
||||
calls++
|
||||
return Completion{Text: `{"name":"Vogel WALL 3245","description":"Vogel's WALL 3245 is a sturdy TV mount with clear load ratings and install guidance for wall displays."}`, TotalTokens: 5}, nil
|
||||
}},
|
||||
Vector: NoopVectorCategorizer{},
|
||||
}
|
||||
normName := "Vogel's WALL 3245 TV Wall Mount"
|
||||
normDesc := "A mount with enough mapped detail for hashing inputs."
|
||||
synthPrior := synthesizeDescriptionFromTitle(normName, "TV Mounts", "en", map[string]any{
|
||||
"brand": "Vogel's", "width": "45 cm", "max_load": "40 kg",
|
||||
})
|
||||
if !company.LooksLikeHeuristicSynthesize(synthPrior) {
|
||||
t.Fatalf("expected invent synth prior, got %q", synthPrior)
|
||||
}
|
||||
sysTpl, userTpl := resolveProductPromptTemplates(ProductInput{})
|
||||
priorHash := HashEnhanceInput("", normName, normDesc, "", "", sysTpl, userTpl, map[string]any{})
|
||||
out, err := e.RunSteps(context.Background(), "co", ProductInput{
|
||||
Name: normName,
|
||||
Description: normDesc,
|
||||
Mapped: map[string]any{"name": normName, "description": normDesc},
|
||||
PriorEnhanceHash: priorHash,
|
||||
PriorProcessedName: normName,
|
||||
PriorProcessedDescription: synthPrior,
|
||||
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("synth prior must force completer, calls=%d", calls)
|
||||
}
|
||||
if out.SkipCreditDebit {
|
||||
t.Fatal("synth prior must not SkipCreditDebit")
|
||||
}
|
||||
joined := strings.Join(out.Notes, ";")
|
||||
if !strings.Contains(joined, "forced re-enhance") {
|
||||
t.Fatalf("expected forced re-enhance note, notes=%v", out.Notes)
|
||||
}
|
||||
if company.LooksLikeHeuristicSynthesize(out.ProcessedDescription) && calls == 1 {
|
||||
// LLM stub returned non-synth copy — ok if still synth after outer repair.
|
||||
}
|
||||
if out.ProcessedDescription == synthPrior {
|
||||
t.Fatalf("must not reuse synth prior: %q", out.ProcessedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_formulaMismatchPriorHashDoesNotSkipReprocess(t *testing.T) {
|
||||
calls := 0
|
||||
e := &Engine{
|
||||
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
|
||||
calls++
|
||||
return Completion{
|
||||
Text: `{"name":"GIGABYTE GS27QC","description":"<h1>GIGABYTE GS27QC</h1><p>Curved gaming monitor with vivid colors.</p><ul><li>width 61 cm</li></ul>"}`,
|
||||
TotalTokens: 6,
|
||||
}, nil
|
||||
}},
|
||||
Vector: NoopVectorCategorizer{},
|
||||
}
|
||||
descTpl := map[string]any{
|
||||
"sections": []any{
|
||||
map[string]any{"type": "h1", "instructions": "Heading"},
|
||||
map[string]any{"type": "p", "instructions": "Body"},
|
||||
map[string]any{"type": "ul", "instructions": "Specs"},
|
||||
},
|
||||
}
|
||||
normName := "GIGABYTE GS27QC"
|
||||
normDesc := "GIGABYTE GS27QC is a gaming monitor with a 61 cm curved panel for immersive play."
|
||||
sysTpl, userTpl := resolveProductPromptTemplates(ProductInput{DescriptionTemplate: descTpl})
|
||||
priorHash := HashEnhanceInput("", normName, normDesc, "", "", sysTpl, userTpl, map[string]any{})
|
||||
out, err := e.RunSteps(context.Background(), "co", ProductInput{
|
||||
Name: normName,
|
||||
Description: normDesc,
|
||||
Mapped: map[string]any{"name": normName, "description": normDesc, "category": "7"},
|
||||
DescriptionTemplate: descTpl,
|
||||
PriorEnhanceHash: priorHash,
|
||||
PriorProcessedName: normName,
|
||||
PriorProcessedDescription: normDesc, // plain prose — formula mismatch
|
||||
Language: "en",
|
||||
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls < 1 {
|
||||
t.Fatalf("formula-mismatch prior must force completer, calls=%d", calls)
|
||||
}
|
||||
if out.SkipCreditDebit {
|
||||
t.Fatal("formula-mismatch must not SkipCreditDebit")
|
||||
}
|
||||
joined := strings.Join(out.Notes, ";")
|
||||
if !strings.Contains(joined, "forced re-enhance") && !strings.Contains(joined, "formula") {
|
||||
// forced note when hash matched; formula repair notes also acceptable
|
||||
if !strings.Contains(joined, "synth") && calls >= 1 {
|
||||
// still ok if LLM ran without note if reason path differed
|
||||
}
|
||||
}
|
||||
for _, tag := range []string{"<h1>", "<p>", "<ul>"} {
|
||||
if !strings.Contains(out.ProcessedDescription, tag) {
|
||||
t.Fatalf("expected formula HTML with %s, got %q", tag, out.ProcessedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_weakPriorHashDoesNotSkipReprocess(t *testing.T) {
|
||||
calls := 0
|
||||
e := &Engine{
|
||||
@@ -344,3 +533,56 @@ func TestRunSteps_failedEnhanceDoesNotPoisonLocalizedHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSteps_emptyLLMDescriptionDoesNotPersistHashViaOriginalFallback(t *testing.T) {
|
||||
// Prod bug: empty LLM description preferred originals and stamped ok+hash.
|
||||
richOriginal := "This durable retail mount includes mounting hardware, load ratings, and install guidance for wall displays."
|
||||
e := &Engine{
|
||||
Completer: stubCompleter{fn: func(system, user string) (Completion, error) {
|
||||
return Completion{Text: `{"name":"Vogel WALL 3245","description":""}`, TotalTokens: 2}, nil
|
||||
}},
|
||||
Vector: NoopVectorCategorizer{},
|
||||
}
|
||||
out, err := e.RunSteps(context.Background(), "co", ProductInput{
|
||||
Name: "Vogel WALL 3245",
|
||||
Description: richOriginal,
|
||||
Mapped: map[string]any{
|
||||
"name": "Vogel WALL 3245",
|
||||
"description": richOriginal,
|
||||
},
|
||||
}, "enhance_only", nil, StepPolicy{AllowAI: true, AllowEPREL: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if h, _ := out.FieldSources[FieldEnhanceInputHash].(string); h != "" {
|
||||
t.Fatalf("empty LLM description must not persist enhance hash (got %q); desc=%q", h, out.ProcessedDescription)
|
||||
}
|
||||
for _, lf := range out.LocalizedContent {
|
||||
if lf.EnhanceInputHash != "" {
|
||||
t.Fatalf("localized hash must stay empty on empty LLM desc: %+v", lf)
|
||||
}
|
||||
}
|
||||
joined := strings.Join(out.Notes, ";")
|
||||
if !strings.Contains(joined, "refused") && !strings.Contains(joined, "synthesized") && !strings.Contains(joined, "empty_description") {
|
||||
t.Fatalf("expected refuse/synthesize note, notes=%v", out.Notes)
|
||||
}
|
||||
if strings.TrimSpace(out.ProcessedDescription) == "" {
|
||||
t.Fatal("expected nonempty fallback/synth description")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLlmEnhanceHardRefuseReason(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := llmEnhanceHardRefuseReason("", "desc long enough"); got != "empty_name" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
if got := llmEnhanceHardRefuseReason("Category:", "desc long enough here for tests"); got != "prompt_leakage_name" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
if got := llmEnhanceHardRefuseReason("Widget", ""); got != "empty_description" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
if got := llmEnhanceHardRefuseReason("Widget", "A durable retail widget for everyday use with clear specs."); got != "" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user