package capturehttp_test import ( "bytes" "context" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "github.com/mathiasbq/hyperguild/ingestion/internal/audit" "github.com/mathiasbq/hyperguild/ingestion/internal/brainstore" "github.com/mathiasbq/hyperguild/ingestion/internal/capture" "github.com/mathiasbq/hyperguild/ingestion/internal/capturehttp" "github.com/mathiasbq/hyperguild/ingestion/internal/classification" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const staticTok = "static-secret" // fakeValidator stands in for the chassis JWT validator. type fakeValidator struct { subject string err error } func (f fakeValidator) Validate(context.Context, string) (string, error) { return f.subject, f.err } type fakeTracker struct{ failCreate bool } func (f fakeTracker) CreateIssue(context.Context, string, string, string) (capture.IssueRef, error) { if f.failCreate { return capture.IssueRef{}, errors.New("gitea down") } return capture.IssueRef{Repo: "hyperguild", Number: 1, URL: "https://git/1"}, nil } func (fakeTracker) CloseIssue(context.Context, string, int, string) (capture.IssueRef, error) { return capture.IssueRef{}, nil } func (fakeTracker) CommentIssue(context.Context, string, int, string) (capture.IssueRef, error) { return capture.IssueRef{}, nil } func newHandler(t *testing.T, v capturehttp.Validator, tr capture.IssueTracker, sovereign []string) *capturehttp.Handler { t.Helper() cfg, err := classification.Load(t.TempDir()) require.NoError(t, err) svc := capture.NewService(brainstore.New(t.TempDir()), tr, nil, cfg, audit.NewSlogSink(nil)) return capturehttp.New(svc, v, staticTok, "local-cli", capturehttp.NewOriginResolver(sovereign)) } func do(t *testing.T, h *capturehttp.Handler, authz string, body any) *httptest.ResponseRecorder { t.Helper() b, _ := json.Marshal(body) req := httptest.NewRequest(http.MethodPost, "/capture", bytes.NewReader(b)) if authz != "" { req.Header.Set("Authorization", authz) } rr := httptest.NewRecorder() h.ServeHTTP(rr, req) return rr } func internalReq() map[string]any { return map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "internal"}, "insights": []map[string]any{{"text": "a fact", "wing": "hyperguild", "hall": "facts"}}, } } func TestUnauthorizedWithoutToken(t *testing.T) { h := newHandler(t, fakeValidator{err: errors.New("no")}, fakeTracker{}, nil) rr := do(t, h, "", internalReq()) assert.Equal(t, http.StatusUnauthorized, rr.Code) } func TestUnauthorizedBadToken(t *testing.T) { h := newHandler(t, fakeValidator{err: errors.New("bad jwt")}, fakeTracker{}, nil) rr := do(t, h, "Bearer wrong", internalReq()) assert.Equal(t, http.StatusUnauthorized, rr.Code) } func TestHappyPathStaticToken(t *testing.T) { h := newHandler(t, nil, fakeTracker{}, nil) rr := do(t, h, "Bearer "+staticTok, map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "internal"}, "insights": []map[string]any{{"text": "a fact", "wing": "hyperguild", "hall": "facts"}}, "tickets": []map[string]any{{"repo": "hyperguild", "action": "create", "title": "t"}}, }) require.Equal(t, http.StatusOK, rr.Code) var rec capture.CaptureReceipt require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rec)) assert.True(t, rec.Insights[0].OK) assert.True(t, rec.Tickets[0].OK) assert.Empty(t, rec.Errors) } func TestConfidentialViaUSNexusRefused(t *testing.T) { // JWT principal not in the sovereign allowlist ⇒ us-nexus; confidential ⇒ 403. h := newHandler(t, fakeValidator{subject: "claudeai-oauth-client"}, fakeTracker{}, nil) rr := do(t, h, "Bearer jwt-token", map[string]any{ "context": map[string]any{"harness": "claudeai-chat", "actor": "mathias", "classification": "confidential"}, "insights": []map[string]any{{"text": "secret", "wing": "client-seb", "hall": "facts"}}, }) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "sovereignty") } func TestConfidentialViaSovereignJWTAllowed(t *testing.T) { // Same confidential payload, but the principal is allowlisted sovereign ⇒ allowed. h := newHandler(t, fakeValidator{subject: "koala-cli"}, fakeTracker{}, []string{"koala-cli"}) rr := do(t, h, "Bearer jwt-token", map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "confidential"}, "insights": []map[string]any{{"text": "secret", "wing": "client-seb", "hall": "facts"}}, }) require.Equal(t, http.StatusOK, rr.Code) } func TestStaticTokenIsSovereignSoConfidentialAllowed(t *testing.T) { h := newHandler(t, nil, fakeTracker{}, nil) rr := do(t, h, "Bearer "+staticTok, map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "confidential"}, "insights": []map[string]any{{"text": "secret", "wing": "client-seb", "hall": "facts"}}, }) assert.Equal(t, http.StatusOK, rr.Code) } func TestValidationRejectedBeforeWrite(t *testing.T) { h := newHandler(t, nil, fakeTracker{}, nil) rr := do(t, h, "Bearer "+staticTok, map[string]any{ "context": map[string]any{"actor": "mathias", "classification": "internal"}, "insights": []map[string]any{{"text": "x", "wing": "hyperguild", "hall": "not-a-hall"}}, }) assert.Equal(t, http.StatusBadRequest, rr.Code) } func TestPartialFailureIs207(t *testing.T) { h := newHandler(t, nil, fakeTracker{failCreate: true}, nil) rr := do(t, h, "Bearer "+staticTok, map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "internal"}, "insights": []map[string]any{{"text": "ok insight", "wing": "hyperguild", "hall": "facts"}}, "tickets": []map[string]any{{"repo": "hyperguild", "action": "create", "title": "fails"}}, }) assert.Equal(t, http.StatusMultiStatus, rr.Code) var rec capture.CaptureReceipt require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rec)) assert.True(t, rec.Insights[0].OK) assert.False(t, rec.Tickets[0].OK) assert.Len(t, rec.Errors, 1) } func TestDryRunWritesNothing(t *testing.T) { h := newHandler(t, nil, fakeTracker{}, nil) rr := do(t, h, "Bearer "+staticTok, map[string]any{ "context": map[string]any{"harness": "claude-code", "actor": "mathias", "classification": "internal"}, "insights": []map[string]any{{"text": "a", "wing": "hyperguild", "hall": "facts"}}, "dry_run": true, }) require.Equal(t, http.StatusOK, rr.Code) var rec capture.CaptureReceipt require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rec)) assert.True(t, rec.DryRun) } func TestCallerCannotForgeOrigin(t *testing.T) { // Even if the body tried to assert a sovereign harness, a us-nexus JWT // principal + confidential ⇒ refused. (Origin is server-derived.) h := newHandler(t, fakeValidator{subject: "claudeai-oauth-client"}, fakeTracker{}, nil) rr := do(t, h, "Bearer jwt", map[string]any{ "context": map[string]any{"harness": "sovereign-soil", "actor": "mathias", "classification": "confidential"}, "insights": []map[string]any{{"text": "secret", "wing": "client-seb", "hall": "facts"}}, }) assert.Equal(t, http.StatusForbidden, rr.Code) }