package ucca import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestGetStringSlice(t *testing.T) { m := map[string]interface{}{ "refs": []interface{}{"a", "b", 3, "c"}, // non-strings are skipped "str": "not-a-list", } got := getStringSlice(m, "refs") if len(got) != 3 || got[0] != "a" || got[2] != "c" { t.Errorf("refs: %v", got) } if getStringSlice(m, "missing") != nil { t.Error("missing key should be nil") } if getStringSlice(m, "str") != nil { t.Error("non-list should be nil") } } func TestTopByScore_DeterministicCap(t *testing.T) { m := map[string]float64{"x": 0.5, "y": 0.9, "z": 0.5, "w": 0.7} got := topByScore(m, 2) if len(got) != 2 || got[0] != "y" || got[1] != "w" { t.Errorf("want [y w], got %v", got) } all := topByScore(m, 10) if all[2] != "x" || all[3] != "z" { // tie 0.5 broken by key string t.Errorf("tie-break not deterministic: %v", all) } } func TestExpandViaGraph_NoSeedsOrRefs(t *testing.T) { c := &LegalRAGClient{} // nil httpClient → must not be called on these paths if out := c.expandViaGraph(context.Background(), "x", nil); out != nil { t.Error("empty hits should return nil") } hits := []qdrantSearchHit{{ID: 1, Score: 0.8, Payload: map[string]interface{}{"citation_unit": "Art. 1 CRA"}}} if out := c.expandViaGraph(context.Background(), "x", hits); len(out) != 1 { t.Errorf("no references → unchanged, got %d", len(out)) } } func TestExpandViaGraph_PullsConnectedNorm(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(map[string]interface{}{ "result": map[string]interface{}{ "points": []map[string]interface{}{ {"id": 99, "payload": map[string]interface{}{ "citation_unit": "CRA Anhang I", "chunk_text": "Sicherheitsanforderungen", "source_class": "binding_law", "authority_weight": 100, "regulation_short": "CRA", }}, }, "next_page_offset": nil, }, }) })) defer srv.Close() c := &LegalRAGClient{qdrantURL: srv.URL, httpClient: srv.Client()} hits := []qdrantSearchHit{ {ID: 1, Score: 0.70, Payload: map[string]interface{}{ "citation_unit": "Art. 13 CRA", "references_out": []interface{}{"CRA Anhang I"}, }}, } out := c.expandViaGraph(context.Background(), "bp_compliance_ce", hits) if len(out) != 2 { t.Fatalf("want 2 hits (seed + connected annex), got %d", len(out)) } var found *qdrantSearchHit for i := range out { if getString(out[i].Payload, "citation_unit") == "CRA Anhang I" { found = &out[i] } } if found == nil { t.Fatal("connected norm CRA Anhang I was not pulled into the pool") } if found.Score < 0.64 || found.Score > 0.66 { // 0.70 seed − 0.05 hop penalty t.Errorf("connected score = %v, want ~0.65", found.Score) } }