/ internal / memory / client_test.go
client_test.go
 1  package memory
 2  
 3  import (
 4  	"context"
 5  	"encoding/json"
 6  	"net"
 7  	"net/http"
 8  	"net/http/httptest"
 9  	"os"
10  	"path/filepath"
11  	"testing"
12  	"time"
13  )
14  
15  func startUDSServer(t *testing.T, handler http.Handler) string {
16  	t.Helper()
17  	// Use a short path under os.TempDir() to stay under macOS sun_path's
18  	// 104-byte limit; t.TempDir() embeds the (long) test name.
19  	dir, err := os.MkdirTemp("", "tlm")
20  	if err != nil {
21  		t.Fatal(err)
22  	}
23  	sock := filepath.Join(dir, "s")
24  	ln, err := net.Listen("unix", sock)
25  	if err != nil {
26  		t.Fatal(err)
27  	}
28  	srv := &httptest.Server{Listener: ln, Config: &http.Server{Handler: handler}}
29  	srv.Start()
30  	t.Cleanup(func() { srv.Close(); os.Remove(sock); os.RemoveAll(dir) })
31  	return sock
32  }
33  
34  func TestClient_QueryHappy(t *testing.T) {
35  	sock := startUDSServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36  		if r.Header.Get("X-Request-ID") == "" {
37  			t.Fatal("missing X-Request-ID")
38  		}
39  		w.Header().Set("X-Request-ID", r.Header.Get("X-Request-ID"))
40  		_ = json.NewEncoder(w).Encode(ResponseEnvelope{
41  			ProtocolVersion: 1,
42  			RequestID:       r.Header.Get("X-Request-ID"),
43  			Reason:          "ok",
44  			Candidates:      []QueryCandidate{{Value: "v"}},
45  		})
46  	}))
47  	c := NewClient(sock, 5*time.Second)
48  	ctx := WithRequestID(context.Background(), "req-test123")
49  	env, class, err := c.Query(ctx, QueryIntent{Mode: ModeDirectRelation, AnchorMentions: []string{"x"}})
50  	if err != nil {
51  		t.Fatal(err)
52  	}
53  	if class != ClassOK || env.RequestID != "req-test123" || len(env.Candidates) != 1 {
54  		t.Fatalf("got %+v class=%v", env, class)
55  	}
56  }
57  
58  func TestClient_AutoMintsRequestID(t *testing.T) {
59  	var seen string
60  	sock := startUDSServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61  		seen = r.Header.Get("X-Request-ID")
62  		_ = json.NewEncoder(w).Encode(ResponseEnvelope{Reason: "ok"})
63  	}))
64  	c := NewClient(sock, 5*time.Second)
65  	_, _, _ = c.Query(context.Background(), QueryIntent{Mode: ModeDirectRelation, AnchorMentions: []string{"x"}})
66  	if len(seen) < 5 || seen[:4] != "req-" {
67  		t.Fatalf("auto-minted ID %q does not match req-<hex>", seen)
68  	}
69  }
70  
71  func TestClient_MalformedJSON_Unavailable(t *testing.T) {
72  	sock := startUDSServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73  		_, _ = w.Write([]byte("{not json"))
74  	}))
75  	c := NewClient(sock, 5*time.Second)
76  	_, class, err := c.Query(context.Background(), QueryIntent{Mode: ModeDirectRelation, AnchorMentions: []string{"x"}})
77  	if class != ClassUnavailable || err == nil {
78  		t.Fatalf("class=%v err=%v", class, err)
79  	}
80  }
81  
82  func TestClient_DialCtxCancel(t *testing.T) {
83  	sock := filepath.Join(t.TempDir(), "missing.sock")
84  	c := NewClient(sock, 5*time.Second)
85  	ctx, cancel := context.WithCancel(context.Background())
86  	cancel()
87  	start := time.Now()
88  	_, class, err := c.Query(ctx, QueryIntent{Mode: ModeDirectRelation, AnchorMentions: []string{"x"}})
89  	if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
90  		t.Fatalf("dial took %v — ctx cancellation not honored", elapsed)
91  	}
92  	if class != ClassUnavailable || err == nil {
93  		t.Fatalf("class=%v err=%v", class, err)
94  	}
95  }