Files
Assistant/tests/research_runtime/search_connector_test.go
m 3c46a5f07f
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 53s
fix: reject irrelevant research excerpts
2026-08-24 06:40:31 +03:00

71 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package research_runtime_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
researchadapter "git.cin.su/m/Assistant/adapters/research"
"git.cin.su/m/Assistant/contracts/epistemics"
)
func TestSearchConnectorReturnsBoundedImmutableEvidence(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`<a class="result__a">Verified title</a><a class="result__snippet">Observed source excerpt</a>`))
}))
defer server.Close()
request := epistemics.WebSearchRequest{Plan: epistemics.ResearchPlan{ID: "plan", Question: "verified source", SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, PropositionKey: "question-1", Query: "verified source"}
connector := researchadapter.SearchConnector{EndpointTemplate: server.URL + "?q=%s", AllowedHosts: map[string]struct{}{"127.0.0.1": {}}, Client: server.Client(), MaxResults: 3}
// The production connector requires HTTPS. The test server is HTTPS too;
// use its host as the allowlisted value and preserve the same transport.
candidates, err := connector.Collect(context.Background(), request, 10)
if err != nil {
t.Fatalf("collect search evidence: %v", err)
}
if len(candidates) != 1 || candidates[0].Statement != "Verified title — Observed source excerpt" {
t.Fatalf("unexpected candidates: %+v", candidates)
}
if candidates[0].Evidence != epistemics.EvidenceObserved || candidates[0].Snapshot.ContentDigest == "" || candidates[0].Excerpt.Text == "" {
t.Fatalf("candidate lost evidence chain: %+v", candidates[0])
}
}
func TestSearchConnectorRejectsUnallowlistedEndpoint(t *testing.T) {
request := epistemics.WebSearchRequest{Plan: epistemics.ResearchPlan{ID: "plan", Question: "q", SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, Query: "q"}
_, err := (researchadapter.SearchConnector{EndpointTemplate: "https://example.invalid/?q=%s"}).Collect(context.Background(), request, 10)
if err != researchadapter.ErrSearchRejected {
t.Fatalf("expected allowlist rejection, got %v", err)
}
}
func TestSearchConnectorParsesConfiguredBingStyleResults(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`<li class="b_algo"><h2><a href="https://source.example">Source title</a></h2><p>Current source excerpt</p></li>`))
}))
defer server.Close()
request := epistemics.WebSearchRequest{Plan: epistemics.ResearchPlan{ID: "plan", Question: "q", SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, PropositionKey: "p", Query: "q"}
connector := researchadapter.SearchConnector{EndpointTemplate: server.URL + "?q=%s", AllowedHosts: map[string]struct{}{"127.0.0.1": {}}, Client: server.Client(), MaxResults: 3}
candidates, err := connector.Collect(context.Background(), request, 10)
if err != nil || len(candidates) != 1 || candidates[0].Statement != "Source title — Current source excerpt" {
t.Fatalf("unexpected Bing-style evidence: candidates=%+v err=%v", candidates, err)
}
}
func TestSearchConnectorDropsIrrelevantResults(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`<li class="b_algo"><h2><a href="https://source.example">Викисловарь</a></h2><p>Статья о грамматике</p></li>`))
}))
defer server.Close()
request := epistemics.WebSearchRequest{Plan: epistemics.ResearchPlan{ID: "plan", Question: "какая погода в Киеве", SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, PropositionKey: "p", Query: "какая погода в Киеве"}
connector := researchadapter.SearchConnector{EndpointTemplate: server.URL + "?q=%s", AllowedHosts: map[string]struct{}{"127.0.0.1": {}}, Client: server.Client(), MaxResults: 3}
candidates, err := connector.Collect(context.Background(), request, 10)
if err != nil {
t.Fatalf("collect search evidence: %v", err)
}
if len(candidates) != 0 {
t.Fatalf("irrelevant result was retained: %+v", candidates)
}
}