package knowledge import "testing" func TestNormalizePlacementReason(t *testing.T) { got := normalizePlacementReason("high", "", "pain", 90) if got == "" || got == "high" { t.Fatalf("expected prose for legacy high, got %q", got) } got = normalizePlacementReason("換季泛紅帖常求溫和修護產品", "", "pain", 85) if got != "換季泛紅帖常求溫和修護產品" { t.Fatalf("expected prose preserved, got %q", got) } } func TestExtractJSONObject(t *testing.T) { valid := `{"nodes":[{"label":"a","evidenceUrls":[]}],"edges":[]}` cases := []struct { name string raw string wantErr bool }{ {name: "plain", raw: valid}, {name: "prefixed text", raw: "分析完成。\n" + valid}, {name: "code fence", raw: "說明如下:\n```json\n" + valid + "\n```"}, {name: "trailing object", raw: valid + `,{"summary":"ignored"}`}, {name: "brace in string", raw: `{"nodes":[{"label":"含{符號}的節點","evidenceUrls":[]}],"edges":[]}`}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { payload, err := extractJSONObject(tc.raw) if tc.wantErr { if err == nil { t.Fatal("expected error") } return } if err != nil { t.Fatal(err) } graph, err := ParseSynthOutput(string(payload), SynthInput{Seed: "種子"}, nil) if err != nil { t.Fatal(err) } if len(graph.Nodes) == 0 { t.Fatal("expected nodes") } }) } } func TestParseSynthOutputPlacementReason(t *testing.T) { raw := `{ "nodes": [ { "label": "換季泛紅", "nodeKind": "symptom", "type": "symptom", "layer": 1, "relation": "溫差變化會讓敏感肌臉頰泛紅刺痛", "placementValue": "求助帖常問舒緩產品,可分享溫和修護的實際經驗", "productFitScore": 82, "evidenceUrls": [] } ], "edges": [] }` graph, err := ParseSynthOutput(raw, SynthInput{Seed: "敏感肌"}, nil) if err != nil { t.Fatal(err) } if len(graph.Nodes) < 2 { t.Fatalf("expected core + parsed node, got %d nodes", len(graph.Nodes)) } node := graph.Nodes[len(graph.Nodes)-1] if node.Relation == "" { t.Fatal("expected relation") } if node.PlacementValue == "" || node.PlacementValue == "high" { t.Fatalf("expected placement prose, got %q", node.PlacementValue) } }