@@ -0,0 +1,94 @@
|
||||
package vikingmemory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"data-terminal-cloud/internal/outbox"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testClient(t *testing.T, handler http.HandlerFunc) *Client {
|
||||
t.Helper()
|
||||
server := httptest.NewTLSServer(handler)
|
||||
t.Cleanup(server.Close)
|
||||
c, err := NewClient(Options{BaseURL: server.URL, ResourceID: "mem-test", APIKey: "test-secret", EventType: "event_v1", ProfileType: "profile_v1", Timeout: time.Second})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.http = server.Client()
|
||||
return c
|
||||
}
|
||||
func TestSearchBindsUserAndRejectsForeignResults(t *testing.T) {
|
||||
calls := 0
|
||||
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
var input map[string]any
|
||||
json.NewDecoder(r.Body).Decode(&input)
|
||||
filter := input["filter"].(map[string]any)
|
||||
if filter["user_id"].([]any)[0] != "alice" || filter["assistant_id"].([]any)[0] != AssistantID || input["resource_id"] != "mem-test" {
|
||||
t.Errorf("incorrect scope: %v", input)
|
||||
}
|
||||
if r.Header.Get("Authorization") != "Bearer test-secret" {
|
||||
t.Error("missing auth")
|
||||
}
|
||||
fmt.Fprint(w, `{"code":0,"data":{"result_list":[{"id":"foreign","user_id":["bob"],"assistant_id":["platform_companion_v1"],"memory_info":{"summary":"must not return"}},{"id":"ours","user_id":["alice"],"assistant_id":["platform_companion_v1"],"memory_info":{"summary":"喜欢茶"}}]}}`)
|
||||
})
|
||||
items, err := c.Search(context.Background(), "alice", "茶", 2)
|
||||
if err != nil || len(items) != 2 || items[0].ID != "ours" || calls != 2 {
|
||||
t.Fatalf("items=%v calls=%d err=%v", items, calls, err)
|
||||
}
|
||||
if _, err = c.Search(context.Background(), "", "", 1); err == nil {
|
||||
t.Fatal("empty identity accepted")
|
||||
}
|
||||
}
|
||||
func TestVikingErrorsDoNotLeakServerBodyOrKey(t *testing.T) {
|
||||
for _, status := range []int{200, 401, 429, 503} {
|
||||
t.Run(fmt.Sprint(status), func(t *testing.T) {
|
||||
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprint(w, `{"code":900,"message":"test-secret private transcript"}`)
|
||||
})
|
||||
err := c.AddSession(context.Background(), "c_test", "alice", []Message{{Role: "user", Content: "test"}}, 1)
|
||||
if err == nil || strings.Contains(err.Error(), "secret") || strings.Contains(err.Error(), "transcript") {
|
||||
t.Fatalf("unsafe error %v", err)
|
||||
}
|
||||
if outbox.IsRetryable(err) != (status != 401) {
|
||||
t.Fatalf("retry classification %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestStableNamespacedIdentityAndSessionIDs(t *testing.T) {
|
||||
if UserID("a", "x") == UserID("b", "x") || UserID("a", "x") == UserID("a", "y") {
|
||||
t.Fatal("identity collision")
|
||||
}
|
||||
id := SessionID("12345678-1234-1234-1234-123456789abc", 1)
|
||||
if !regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,127}$`).MatchString(id) || id == SessionID("12345678-1234-1234-1234-123456789abc", 0) {
|
||||
t.Fatal(id)
|
||||
}
|
||||
}
|
||||
func TestDeleteUsesExactUserForBothMemoryTypes(t *testing.T) {
|
||||
calls := 0
|
||||
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
var input map[string]any
|
||||
json.NewDecoder(r.Body).Decode(&input)
|
||||
f := input["filter"].(map[string]any)
|
||||
if f["user_id"].([]any)[0] != "alice" {
|
||||
t.Error("unscoped delete")
|
||||
}
|
||||
fmt.Fprint(w, `{"code":0}`)
|
||||
})
|
||||
if err := c.DeleteUser(context.Background(), ""); err == nil || calls != 0 {
|
||||
t.Fatal("empty deletion accepted")
|
||||
}
|
||||
if err := c.DeleteUser(context.Background(), "alice"); err != nil || calls != 2 {
|
||||
t.Fatal(err, calls)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user