package main import ( "encoding/json" "net/http" "net/http/httptest" "sync" "testing" "time" ) // capturedRequest is a recorded inbound request against a fake ecosystem // server, kept minimal (method/path/body) since that's what tests assert on. type capturedRequest struct { Method string Path string Body []byte } // fakeServer is the common shell behind fakeNexus/fakePraxis/fakeHexis: an // httptest.Server whose responses are driven by a caller-supplied route // table, with every inbound request captured for later assertions and an // optional fault (status code) that overrides all routing while active — // the degraded-mode lever for these tests. type fakeServer struct { *httptest.Server mu sync.Mutex requests []capturedRequest fault int // non-zero: every request gets this HTTP status instead of routing } // newFakeServer starts a server dispatching to routes keyed by "METHOD // PATH_PREFIX" (matched with HasPrefix on path), falling back to notFound // then to a 404 if no route matches. Route handlers are plain // http.HandlerFuncs so tests can write arbitrary fixture responses. func newFakeServer(t *testing.T, routes map[string]http.HandlerFunc) *fakeServer { t.Helper() fs := &fakeServer{} fs.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body := make([]byte, 0) if r.Body != nil { buf := make([]byte, r.ContentLength) if r.ContentLength > 0 { n, _ := r.Body.Read(buf) body = buf[:n] } } fs.mu.Lock() fs.requests = append(fs.requests, capturedRequest{Method: r.Method, Path: r.URL.Path, Body: body}) fault := fs.fault fs.mu.Unlock() if fault != 0 { http.Error(w, "injected fault", fault) return } for key, handler := range routes { method, prefix := splitRouteKey(key) if r.Method == method && hasPrefix(r.URL.Path, prefix) { handler(w, r) return } } http.NotFound(w, r) })) t.Cleanup(fs.Server.Close) return fs } func splitRouteKey(key string) (method, prefix string) { for i := 0; i < len(key); i++ { if key[i] == ' ' { return key[:i], key[i+1:] } } return key, "" } func hasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } // SetFault makes every subsequent request fail closed with the given HTTP // status, simulating the dependency being down. Pass 0 to clear it. func (fs *fakeServer) SetFault(status int) { fs.mu.Lock() defer fs.mu.Unlock() fs.fault = status } // Requests returns a snapshot of captured requests, in arrival order. func (fs *fakeServer) Requests() []capturedRequest { fs.mu.Lock() defer fs.mu.Unlock() out := make([]capturedRequest, len(fs.requests)) copy(out, fs.requests) return out } func jsonHandler(status int, body string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) w.Write([]byte(body)) } } // --- Protocol fixtures --- // Canned response bodies matching each service's documented contract shape, // so individual tests don't hand-roll JSON literals for the common cases. func fixtureNexusResolved(entityID, displayName, entityType string) string { return mustJSON(map[string]any{ "status": "resolved", "entity": map[string]any{"id": entityID, "display_name": displayName, "type": entityType}, }) } func fixtureNexusNotFound() string { return `{"status":"not_found"}` } func fixtureNexusAmbiguous(candidates ...map[string]string) string { cs := make([]map[string]any, len(candidates)) for i, c := range candidates { cs[i] = map[string]any{"entity_id": c["entity_id"], "display_name": c["display_name"]} } return mustJSON(map[string]any{"status": "ambiguous", "candidates": cs}) } func fixtureHexisCapabilities(caps ...map[string]any) string { return mustJSON(caps) } func fixtureHexisExecuted(id, status string) string { return mustJSON(map[string]any{"id": id, "status": status}) } func fixturePraxisAttentionItems(items ...map[string]any) string { return mustJSON(items) } func mustJSON(v any) string { b, err := json.Marshal(v) if err != nil { panic(err) } return string(b) } // --- Fake clock --- // A settable time source satisfying the `func() time.Time` shape reactiveHandler // takes as `now`, so tests can control "now" independently of wall-clock time // (e.g. asserting age-based digest ordering without sleeping). type fakeClock struct { mu sync.Mutex t time.Time } func newFakeClock(start time.Time) *fakeClock { return &fakeClock{t: start} } func (c *fakeClock) Now() time.Time { c.mu.Lock() defer c.mu.Unlock() return c.t } func (c *fakeClock) Advance(d time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.t = c.t.Add(d) } // --- Convenience constructors for the three ecosystem services --- // newFakeNexus starts a fake Nexus exposing /api/v1/resolve. resolveBody is // returned verbatim (200) for every resolve call unless a fault is injected. func newFakeNexus(t *testing.T, resolveBody string) *fakeServer { return newFakeServer(t, map[string]http.HandlerFunc{ "POST /api/v1/resolve": jsonHandler(http.StatusOK, resolveBody), }) } // newFakePraxis starts a fake Praxis exposing the /api/v1/tools/* surface // Maven's praxisClient calls. Every route returns its fixed body until a // fault is injected via SetFault. func newFakePraxis(t *testing.T, attentionBody string) *fakeServer { return newFakeServer(t, map[string]http.HandlerFunc{ "GET /api/v1/tools/attention": jsonHandler(http.StatusOK, attentionBody), "POST /api/v1/tools/surface": jsonHandler(http.StatusOK, `{}`), }) } // newFakeHexis starts a fake Hexis exposing /api/v1/capabilities and // /api/v1/execute. func newFakeHexis(t *testing.T, capsBody, executeBody string) *fakeServer { return newFakeServer(t, map[string]http.HandlerFunc{ "GET /api/v1/capabilities": jsonHandler(http.StatusOK, capsBody), "POST /api/v1/execute": jsonHandler(http.StatusOK, executeBody), }) }