package attesto import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) const testAPIKey = "atto_test_0123456789abcdef0123456789abcdef" func TestClientCallsProductionV2Endpoints(t *testing.T) { var seen []string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seen = append(seen, r.Method+" "+r.URL.String()) if r.Header.Get("Authorization") != "Bearer "+testAPIKey { t.Fatalf("authorization header missing") } if !strings.HasPrefix(r.Header.Get("X-Attesto-SDK"), "attesto-go/") { t.Fatalf("sdk header missing: %s", r.Header.Get("X-Attesto-SDK")) } w.Header().Set("Content-Type", "application/json") switch { case r.Method == http.MethodPost && r.URL.Path == "/v2/streams": json.NewEncoder(w).Encode(Stream{ StreamID: "str_123", SystemID: "sys_123", UseCase: "ai-governance", PolicyID: "policy-main", Status: "active", Created: true, }) case r.Method == http.MethodPost && r.URL.Path == "/v2/streams/str_123/events": if r.Header.Get("Idempotency-Key") == "" { t.Fatalf("idempotency key missing") } var payload map[string]any if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { t.Fatalf("decode event body: %v", err) } if payload["occurredAt"] == "" { t.Fatalf("occurredAt missing from event body") } json.NewEncoder(w).Encode(EventReceipt{ StreamID: "str_123", StreamEventID: "evt_123", SeqNo: 1, EventHash: strings.Repeat("a", 64), StreamHeadHash: strings.Repeat("b", 64), Receipt: SignedReceipt{Payload: M{}, ReceiptHash: strings.Repeat("c", 64), Signature: ReceiptSignature{Alg: "ed25519"}}, }) case r.Method == http.MethodPost && r.URL.Path == "/v2/verify": json.NewEncoder(w).Encode(VerifyReport{Kind: VerifyReceipt, OK: true, Problems: []string{}, Result: "accepted"}) default: t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String()) } })) defer server.Close() client, err := NewClient(testAPIKey, WithBaseURL(server.URL), WithMaxRetries(1)) if err != nil { t.Fatalf("client: %v", err) } ctx := context.Background() stream, err := client.CreateStream(ctx, StreamCreateInput{UseCase: "ai-governance", PolicyID: "policy-main"}) if err != nil { t.Fatalf("create stream: %v", err) } if stream.StreamID != "str_123" { t.Fatalf("stream id mismatch: %s", stream.StreamID) } receipt, err := client.LogEvent(ctx, "str_123", EventInput{SourceRef: "go-test"}) if err != nil { t.Fatalf("log event: %v", err) } if receipt.StreamEventID != "evt_123" { t.Fatalf("receipt id mismatch: %s", receipt.StreamEventID) } report, err := client.VerifyObjectRemote(ctx, OfflineVerifyInput{Kind: VerifyReceipt, Object: M{"receipt": "object"}}) if err != nil { t.Fatalf("verify object: %v", err) } if !report.OK { t.Fatalf("verify report failed: %#v", report) } if len(seen) != 3 { t.Fatalf("request count mismatch: %v", seen) } } func TestBearerClientCanCallTenantEndpoints(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "Bearer tenant-token" { t.Fatalf("tenant bearer header missing") } if r.URL.Path != "/v2/tenant/streams" { t.Fatalf("unexpected path: %s", r.URL.Path) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode([]TenantStream{{StreamID: "str_tenant", SystemID: "sys_123", UseCase: "audit", PolicyID: "policy", Status: "active"}}) })) defer server.Close() client, err := NewBearerClient("tenant-token", WithBaseURL(server.URL), WithMaxRetries(1)) if err != nil { t.Fatalf("client: %v", err) } streams, err := client.ListTenantStreams(context.Background(), "", 100, 0) if err != nil { t.Fatalf("list streams: %v", err) } if len(streams) != 1 || streams[0].StreamID != "str_tenant" { t.Fatalf("unexpected streams: %#v", streams) } }