The GitHub account that hosted this repository is suspended, so the remote is dead and the vanity import github.com/ZacharyZhang-NY/ely-eye/... no longer resolves. The Go module path is that import, so it moves with the host: git.zacharyzhang.com serves a go-import meta tag for the nested ely-eye-mcp submodule, which go build and go get both need. The installers read release assets from the host too, so their API and download URLs move with it. The release workflow's -ldflags symbol path is the module path, so it moves as well; a build stamped with the new path prints the stamped version.
110 lines
4.6 KiB
Go
110 lines
4.6 KiB
Go
package mcpserver
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/apiclient"
|
|
)
|
|
|
|
// askingServer records the path and query string of every request so a test can
|
|
// prove the tool asked for the session it was given, not a guess built from it.
|
|
func askingServer(t *testing.T, body string, seen *[]string) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
*seen = append(*seen, r.URL.Path+"?"+r.URL.RawQuery)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(body))
|
|
}))
|
|
}
|
|
|
|
func TestWorkingSetAsksForTheSessionAndKeepsWhatTheStoreSaw(t *testing.T) {
|
|
var seen []string
|
|
server := askingServer(t, `[
|
|
{"atom_id":"mem-1:text","holder_id":"妻子","session_id":"cooking-2026-10","text":"钥匙在玄关抽屉","recall_count":3,"importance":null,"last_recalled_at":null},
|
|
{"atom_id":"mem-2:text","holder_id":"妻子","session_id":"cooking-2026-10","text":"恒温器调到 24 度","recall_count":0,"importance":0.8,"last_recalled_at":"2026-10-09T04:00:00+00:00"}
|
|
]`, &seen)
|
|
defer server.Close()
|
|
|
|
s := &Server{api: apiclient.New(server.URL)}
|
|
_, out, err := s.workingSetTool(context.Background(), nil, WorkingSetInput{Holder: "妻子", Session: "cooking-2026-10"})
|
|
if err != nil {
|
|
t.Fatalf("working_set failed: %v", err)
|
|
}
|
|
if len(seen) != 1 || seen[0] != "/api/memory/working?holder=%E5%A6%BB%E5%AD%90&session=cooking-2026-10" {
|
|
t.Fatalf("the tool asked %v, want the holder and session encoded on the query", seen)
|
|
}
|
|
if len(out.Atoms) != 2 || out.Atoms[0].Text != "钥匙在玄关抽屉" || out.Atoms[1].Text != "恒温器调到 24 度" {
|
|
t.Fatalf("working set = %+v, want the store's order kept", out.Atoms)
|
|
}
|
|
if out.Atoms[0].SessionID != "cooking-2026-10" || out.Atoms[0].RecallCount != 3 {
|
|
t.Errorf("a working set that drops the session id or the recall count is half-blind, got %+v", out.Atoms[0])
|
|
}
|
|
if out.Atoms[0].Importance != nil {
|
|
t.Errorf("a null importance became %v, want it left unset", *out.Atoms[0].Importance)
|
|
}
|
|
if out.Atoms[1].Importance == nil || *out.Atoms[1].Importance != 0.8 {
|
|
t.Errorf("importance = %v, want the stated 0.8", out.Atoms[1].Importance)
|
|
}
|
|
}
|
|
|
|
func TestWorkingSetRefusesARequestWithoutASession(t *testing.T) {
|
|
var seen []string
|
|
server := askingServer(t, `[]`, &seen)
|
|
defer server.Close()
|
|
|
|
s := &Server{api: apiclient.New(server.URL)}
|
|
if _, _, err := s.workingSetTool(context.Background(), nil, WorkingSetInput{Holder: "wife"}); err == nil {
|
|
t.Fatal("a working set without a session was accepted, but a session is what makes it a working set")
|
|
}
|
|
if len(seen) != 0 {
|
|
t.Errorf("the tool reached for the network with %v before refusing, want the request refused locally", seen)
|
|
}
|
|
}
|
|
|
|
func TestMemoryConflictsNamesBothSidesOfTheDisagreement(t *testing.T) {
|
|
var seen []string
|
|
server := askingServer(t, `[
|
|
{"conflict_id":7,"older_atom_id":"mem-1:text","newer_atom_id":"mem-2:text","holder_id":"wife","scenario":"home","terms":["恒温"],"older_value":"22","newer_value":"24","detected_at":"2026-10-09T04:00:00+00:00","resolved_at":null}
|
|
]`, &seen)
|
|
defer server.Close()
|
|
|
|
s := &Server{api: apiclient.New(server.URL)}
|
|
_, out, err := s.conflictsTool(context.Background(), nil, ConflictsInput{Holder: "wife"})
|
|
if err != nil {
|
|
t.Fatalf("memory_conflicts failed: %v", err)
|
|
}
|
|
if len(seen) != 1 || seen[0] != "/api/memory/conflicts?holder=wife" {
|
|
t.Fatalf("the tool asked %v, want the holder on the query", seen)
|
|
}
|
|
if len(out.Conflicts) != 1 {
|
|
t.Fatalf("conflicts = %+v, want the one unsettled pair", out.Conflicts)
|
|
}
|
|
record := out.Conflicts[0]
|
|
if record.OlderAtomID == "" || record.NewerAtomID == "" || record.OlderValue != "22" || record.NewerValue != "24" {
|
|
t.Errorf("conflict = %+v, want both sides and both values so the caller can pick one", record)
|
|
}
|
|
if len(record.Terms) != 1 || record.Terms[0] != "恒温" {
|
|
t.Errorf("terms = %v, want the term the two memories disagree about", record.Terms)
|
|
}
|
|
if record.ResolvedAt != "" {
|
|
t.Errorf("resolved_at = %q, want an unsettled conflict reading as unresolved", record.ResolvedAt)
|
|
}
|
|
}
|
|
|
|
func TestMemoryConflictsWithoutAHolderAsksForEveryHolder(t *testing.T) {
|
|
var seen []string
|
|
server := askingServer(t, `[]`, &seen)
|
|
defer server.Close()
|
|
|
|
s := &Server{api: apiclient.New(server.URL)}
|
|
if _, _, err := s.conflictsTool(context.Background(), nil, ConflictsInput{}); err != nil {
|
|
t.Fatalf("memory_conflicts failed: %v", err)
|
|
}
|
|
if len(seen) != 1 || seen[0] != "/api/memory/conflicts?" {
|
|
t.Fatalf("the tool asked %v, want no holder filter so the whole store is listed", seen)
|
|
}
|
|
}
|