package processing import ( "strings" "testing" "github.com/descrybe/descrybe-v2/apps/api/internal/aiprompts" "github.com/descrybe/descrybe-v2/apps/api/internal/company" ) func TestResolveProductPromptTemplates_categoryOverridesUser(t *testing.T) { t.Parallel() sys, user := resolveProductPromptTemplates(ProductInput{ EnhanceSystemTemplate: "sys {{brand_voice}}", EnhanceUserTemplate: "company user", CategoryEnhancePrompt: "category user {{description}}", }) if !strings.Contains(sys, "sys {{brand_voice}}") { t.Fatalf("system=%q want company system kept", sys) } if !strings.Contains(sys, `apply it to "name", "description", and "attrs"`) { t.Fatalf("system=%q want category title+description+attrs overlay", sys) } if !strings.Contains(user, "category user {{description}}") { t.Fatalf("user=%q want category override text", user) } if !strings.Contains(user, "applies to name, description, and attrs") { t.Fatalf("user=%q want title+description+attrs framing", user) } if !strings.Contains(user, "{{attrs}}") { t.Fatalf("user=%q want injected {{attrs}}", user) } if !strings.Contains(user, "{{name}}") || !strings.Contains(user, "{{category}}") { t.Fatalf("user=%q want name/category placeholders", user) } } func TestResolveProductPromptTemplates_categoryWithAttrsUnchanged(t *testing.T) { t.Parallel() _, user := resolveProductPromptTemplates(ProductInput{ CategoryEnhancePrompt: "Write copy.\nAttrs: {{attrs}}\nName: {{name}}", }) if !strings.Contains(user, "Write copy.\nAttrs: {{attrs}}\nName: {{name}}") { t.Fatalf("user=%q want category text preserved when attrs present", user) } if !strings.Contains(user, "applies to name, description, and attrs") { t.Fatalf("user=%q want title framing on free-form overlay", user) } } func TestResolveProductPromptTemplates_categoryCanonicalSkipsFrame(t *testing.T) { t.Parallel() _, user := resolveProductPromptTemplates(ProductInput{ CategoryEnhancePrompt: aiprompts.CategoryEnhanceUserTemplate, }) if strings.Count(user, "applies to name, description, and attrs") != 0 || strings.Count(user, "applies to name and description") != 0 { t.Fatalf("canonical template must not get double framing: %q", user) } if !strings.Contains(user, "Title formula") || !strings.Contains(user, `"name"`) { t.Fatalf("canonical template should retain name guidance: %q", user) } } func TestResolveProductPromptTemplates_categoryWithTitleFormula(t *testing.T) { t.Parallel() title := map[string]any{ "separator": " ", "elements": []any{ map[string]any{"type": "variable", "value": "brand"}, map[string]any{"type": "variable", "value": "product_model"}, }, } desc := map[string]any{ "sections": []any{ map[string]any{"type": "p", "instructions": "Factual summary"}, }, } sys, user := resolveProductPromptTemplates(ProductInput{ EnhanceSystemTemplate: "Retail copywriter.\n- name: short retail title\n- description: 1-2 sentences", CategoryEnhancePrompt: "Emphasize energy class and Slovenian retail tone.", TitleTemplate: title, DescriptionTemplate: desc, }) if !strings.Contains(user, "Emphasize energy class") { t.Fatalf("missing category prompt: %s", user) } if !strings.Contains(user, "applies to name, description, and attrs") { t.Fatalf("missing category title framing: %s", user) } if !strings.Contains(user, "Title formula") || !strings.Contains(user, "attr [brand]") { t.Fatalf("missing title formula: %s", user) } if !strings.Contains(user, "Description formula") || !strings.Contains(user, "- p: Factual summary") { t.Fatalf("missing description formula: %s", user) } if !strings.Contains(sys, `When the user message includes a "Title formula"`) { t.Fatalf("missing title system override: %s", sys) } if !strings.Contains(sys, "Description formula") { t.Fatalf("missing description system override: %s", sys) } if !strings.Contains(sys, `apply it to "name", "description", and "attrs"`) { t.Fatalf("missing category system overlay: %s", sys) } } func TestResolveProductPromptTemplates_derivesFormulaFromPromptHTML(t *testing.T) { t.Parallel() prompt := aiprompts.SectionTitleStart + "\n" + `Napiši novo ime izdelka po formuli: "tip izdelka sentence case", "znamka", "model". Ne uporabljaj vejic.` + "\n" + aiprompts.SectionTitleEnd + "\n\n" + aiprompts.SectionDescriptionStart + "\n" + `

{Napiši Novo ime izdelka in izpostavi en benefit}

` + `

{Napiši odstavek, ki je dolg 100 besed.}

` + `{Tehnične specifikacije}` + "\n" + aiprompts.SectionDescriptionEnd sys, user := resolveProductPromptTemplates(ProductInput{ EnhanceSystemTemplate: "Retail copywriter.\n- description: 1-2 sentences", CategoryEnhancePrompt: prompt, // Empty structured template — production A1 path often only has prompt HTML. DescriptionTemplate: nil, }) if !strings.Contains(user, "Description formula") { t.Fatalf("expected Description formula derived from prompt HTML:\n%s", user) } if !strings.Contains(user, "- h2:") || !strings.Contains(user, "- p:") || !strings.Contains(user, "- ul:") { t.Fatalf("expected h2/p/ul sections in formula:\n%s", user) } if !strings.Contains(sys, "Description formula") { t.Fatalf("missing description system override:\n%s", sys) } if !strings.Contains(sys, `"name" MUST be rewritten`) { t.Fatalf("missing title rewrite system override:\n%s", sys) } if descriptionSatisfiesFormula("plain Slovenian prose without tags", aiprompts.EffectiveDescriptionTemplateAny(nil, prompt)) { t.Fatal("plain prose must fail derived formula gate") } } func TestResolveProductPromptTemplates_fallsBackToCompany(t *testing.T) { t.Parallel() _, user := resolveProductPromptTemplates(ProductInput{ EnhanceUserTemplate: "company user {{name}}", }) if user != "company user {{name}}" { t.Fatalf("user=%q", user) } } func TestRenderProductEnhancePrompts_categoryOverrideGetsAttrs(t *testing.T) { t.Parallel() sysTpl, userTpl := resolveProductPromptTemplates(ProductInput{ CategoryEnhancePrompt: "Write a monitor listing. Focus on panel tech.", }) _, user := RenderProductEnhancePrompts( sysTpl, userTpl, "Monitors", "UltraView 27", "", "", "", "en", map[string]any{"brand": "Acme", "size": "27 inch"}, ) if !strings.Contains(user, "Attrs:") { t.Fatalf("user missing Attrs block:\n%s", user) } if !strings.Contains(user, "Acme") || !strings.Contains(user, "27") { t.Fatalf("user missing attr values:\n%s", user) } if !strings.Contains(user, thinEnhanceUserInstruction) { t.Fatalf("thin input should prepend instruction:\n%s", user) } if !strings.Contains(user, "UltraView 27") { t.Fatalf("user missing name:\n%s", user) } } func TestCategoryEnhancePromptFor(t *testing.T) { t.Parallel() m := map[string]company.LangPromptMap{ "monitorji": {"sl": "prompt-a", "en": "prompt-a-en"}, "televizorji": {"sl": "prompt-b"}, } if got := categoryEnhancePromptFor(m, " Monitorji ", "sl", "sl"); got != "prompt-a" { t.Fatalf("got %q", got) } if got := categoryEnhancePromptFor(m, " Monitorji ", "en", "sl"); got != "prompt-a-en" { t.Fatalf("got %q", got) } if got := categoryEnhancePromptFor(m, "missing", "sl", "sl"); got != "" { t.Fatalf("expected empty, got %q", got) } // sl-only category: en falls back to primary=sl if got := categoryEnhancePromptFor(m, "televizorji", "en", "sl"); got != "prompt-b" { t.Fatalf("en→primary sl: got %q", got) } if got := categoryEnhancePromptFor(m, "televizorji", "en", "en"); got != "" { t.Fatalf("en with primary=en should stay empty, got %q", got) } } func TestCategoryEnhancePromptFor_uniqueIDAndNameKeys(t *testing.T) { t.Parallel() overlay := company.LangPromptMap{"*": aiprompts.CategoryEnhanceUserTemplate} byName := map[string]company.LangPromptMap{ "štedilniki": overlay, } byUID := map[string]company.LangPromptMap{ "50": overlay, } if got := categoryEnhancePromptFor(byName, "50", "sl", "sl"); got != "" { t.Fatalf("name-only map must miss unique_id lookup, got %q", got) } if got := categoryEnhancePromptFor(byUID, "50", "sl", "sl"); !strings.Contains(got, "{{attrs}}") { t.Fatalf("unique_id key should resolve overlay: %q", got) } // Dual-index map (as loadCategoryEnhanceOverlays builds): unique_id OR name works. dual := map[string]company.LangPromptMap{} for _, k := range categoryOverlayKeys("50", "Štedilniki") { dual[k] = overlay } if got := categoryEnhancePromptFor(dual, "50", "en", "sl"); !strings.Contains(got, "{{category}}") { t.Fatalf("dual unique_id lookup: %q", got) } if got := categoryEnhancePromptFor(dual, "Štedilniki", "en", "sl"); !strings.Contains(got, "{{attrs}}") { t.Fatalf("dual name lookup: %q", got) } } func TestCategoryOverlayKeys_indexesUIDAndName(t *testing.T) { t.Parallel() keys := categoryOverlayKeys("50", "Štedilniki") want := map[string]bool{"50": true, "štedilniki": true} if len(keys) != 2 { t.Fatalf("keys=%v want 2", keys) } for _, k := range keys { if !want[k] { t.Fatalf("unexpected key %q in %v", k, keys) } } } func TestRenderProductEnhancePrompts_languageLabelsSLEN(t *testing.T) { t.Parallel() sysTpl, userTpl := resolveProductPromptTemplates(ProductInput{}) sysSL, _ := RenderProductEnhancePrompts(sysTpl, userTpl, "Cat", "Name", "Desc", "", "", "sl", nil) if !strings.Contains(sysSL, "Slovenian") { t.Fatalf("sl system missing Slovenian label:\n%s", sysSL) } sysEN, _ := RenderProductEnhancePrompts(sysTpl, userTpl, "Cat", "Name", "Desc", "", "", "en", nil) if !strings.Contains(sysEN, "English") { t.Fatalf("en system missing English label:\n%s", sysEN) } }