p3: tavily parse errors degrade; retry DNS on search

A 200 with a bad Tavily body is tavily: error, not an empty success
that would cache for the hour. Upstream clients stay in the source
list if DNS is down at boot and retry the pin on Search; /healthz
lists them as unresolved until a pin lands.
This commit is contained in:
2026-09-23 12:22:31 -07:00
parent f748e695b1
commit 429f6b207e
8 changed files with 177 additions and 51 deletions
+52 -6
View File
@@ -2,16 +2,20 @@ package dial_test
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"net/netip"
"net/url"
"strings"
"sync/atomic"
"testing"
"git.wntrmute.dev/kyle/sift/internal/search/dial"
)
var errDNS = errors.New("dns down")
func TestLoopbackAllowsConfiguredPort(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
@@ -53,25 +57,31 @@ func TestLoopbackRefusesOtherPort(t *testing.T) {
}
func TestPublicPoisonedPrivateRefused(t *testing.T) {
_, err := dial.New("https://api.tavily.com/search", dial.Options{
c, err := dial.New("https://api.tavily.com/search", dial.Options{
Loopback: false,
Lookup: func(_ context.Context, host string) ([]netip.Addr, error) {
return []netip.Addr{netip.MustParseAddr("127.0.0.1")}, nil
},
})
if err == nil {
t.Fatal("poisoned A record allowed")
if err != nil {
t.Fatal(err)
}
if c.Ready() {
t.Fatal("poisoned A record pinned")
}
}
func TestPublicPrivateRFC1918Refused(t *testing.T) {
_, err := dial.New("https://api.search.brave.com/res/v1/llm/context", dial.Options{
c, err := dial.New("https://api.search.brave.com/res/v1/llm/context", dial.Options{
Lookup: func(_ context.Context, _ string) ([]netip.Addr, error) {
return []netip.Addr{netip.MustParseAddr("10.0.0.5")}, nil
},
})
if err == nil {
t.Fatal("private A allowed")
if err != nil {
t.Fatal(err)
}
if c.Ready() {
t.Fatal("private A pinned")
}
}
@@ -110,3 +120,39 @@ func TestParseRejectsNonHTTP(t *testing.T) {
}
_ = url.URL{}
}
func TestResolveRetriesAfterDNSDown(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
defer ts.Close()
u, _ := url.Parse(ts.URL)
ip := netip.MustParseAddr(u.Hostname())
var n atomic.Int32
c, err := dial.New(ts.URL, dial.Options{
Loopback: true,
Lookup: func(_ context.Context, host string) ([]netip.Addr, error) {
if n.Add(1) == 1 {
return nil, errDNS
}
return []netip.Addr{ip}, nil
},
})
if err != nil {
t.Fatal(err)
}
if c.Ready() {
t.Fatal("first lookup should fail")
}
if err := c.Ensure(context.Background()); err != nil {
t.Fatal(err)
}
if !c.Ready() {
t.Fatal("retry")
}
resp, err := c.HTTP.Get(ts.URL + "/")
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
}