From 06d2893ff12fdaa5f579639b703b0e5ba461384d Mon Sep 17 00:00:00 2001 From: arreyder Date: Thu, 14 May 2026 09:37:25 -0500 Subject: [PATCH 1/2] fix(events): annotate ListEvents errors with status / request id / window The vendored okta-sdk-golang *okta.Error formatter returns the literal string "the API returned an unknown error" when neither ErrorDescription nor ErrorSummary is populated (HTML 5xx pages, gateway timeouts, non-JSON bodies). It also drops HTTP status and X-Okta-Request-Id. Errors arriving in prod traces were undiagnosable. Wrap GetLogs errors with HTTP status, X-Okta-Request-Id, and the request's `since` / `after` query window. Uses %w so callers can still errors.Is / errors.As the inner *okta.Error. Adds event_log_test.go covering: - Full-context wrap (status + request_id + window) - Nil response (early network failure) - Missing optional fields elided cleanly Refs OPS-1539 Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/connector/event_log.go | 26 +++++++++++++- pkg/connector/event_log_test.go | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 pkg/connector/event_log_test.go diff --git a/pkg/connector/event_log.go b/pkg/connector/event_log.go index 186e30322..94a75eb4d 100644 --- a/pkg/connector/event_log.go +++ b/pkg/connector/event_log.go @@ -10,6 +10,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/annotations" "github.com/conductorone/baton-sdk/pkg/pagination" "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "github.com/okta/okta-sdk-golang/v2/okta" "github.com/okta/okta-sdk-golang/v2/okta/query" "go.uber.org/zap" "google.golang.org/protobuf/types/known/timestamppb" @@ -68,7 +69,7 @@ func (connector *Okta) ListEvents( logs, resp, err := connector.client.LogEvent.GetLogs(ctx, qp) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, wrapListEventsError(qp, resp, err) } // MJP each log is not guaranteed to result in a v2.Event anymore, but it's still likely? @@ -102,3 +103,26 @@ func (connector *Okta) ListEvents( return rv, streamState, annos, nil } + +// wrapListEventsError annotates errors from the Okta /api/v1/logs endpoint with HTTP +// status, X-Okta-Request-Id, and the query window. The vendored okta-sdk-golang's +// *okta.Error formatter returns the literal string "the API returned an unknown error" +// whenever neither ErrorDescription nor ErrorSummary is populated (HTML 5xx pages, +// gateway timeouts, non-JSON bodies), so without this enrichment the failures are +// undiagnosable from traces alone. +func wrapListEventsError(qp *query.Params, resp *okta.Response, err error) error { + parts := []string{"okta-connectorv2: ListEvents failed"} + if resp != nil && resp.Response != nil { + parts = append(parts, fmt.Sprintf("status=%d", resp.StatusCode)) + if rid := resp.Header.Get("X-Okta-Request-Id"); rid != "" { + parts = append(parts, fmt.Sprintf("request_id=%s", rid)) + } + } + if qp != nil && qp.Since != "" { + parts = append(parts, fmt.Sprintf("since=%s", qp.Since)) + } + if qp != nil && qp.After != "" { + parts = append(parts, fmt.Sprintf("after=%s", qp.After)) + } + return fmt.Errorf("%s: %w", strings.Join(parts, " "), err) +} diff --git a/pkg/connector/event_log_test.go b/pkg/connector/event_log_test.go new file mode 100644 index 000000000..c9485a01f --- /dev/null +++ b/pkg/connector/event_log_test.go @@ -0,0 +1,63 @@ +package connector + +import ( + "errors" + "net/http" + "testing" + + "github.com/okta/okta-sdk-golang/v2/okta" + "github.com/okta/okta-sdk-golang/v2/okta/query" + "github.com/stretchr/testify/require" +) + +func TestWrapListEventsError_FullContext(t *testing.T) { + // On an Okta SDK error with a non-nil response, the wrapper must surface HTTP status, + // X-Okta-Request-Id, and the query window — otherwise the inner error reduces to the + // useless "the API returned an unknown error" sentinel from okta-sdk-golang. + header := http.Header{} + header.Set("X-Okta-Request-Id", "req-abc123") + resp := &okta.Response{Response: &http.Response{StatusCode: 502, Header: header}} + qp := &query.Params{Since: "2026-05-13T00:00:00Z", After: "0oa1cursor"} + inner := errors.New("the API returned an unknown error") + + wrapped := wrapListEventsError(qp, resp, inner) + require.Error(t, wrapped) + msg := wrapped.Error() + require.Contains(t, msg, "okta-connectorv2: ListEvents failed") + require.Contains(t, msg, "status=502") + require.Contains(t, msg, "request_id=req-abc123") + require.Contains(t, msg, "since=2026-05-13T00:00:00Z") + require.Contains(t, msg, "after=0oa1cursor") + require.ErrorIs(t, wrapped, inner, "wrapper must preserve errors.Is on the inner error") +} + +func TestWrapListEventsError_NilResponse(t *testing.T) { + // On a network failure before any HTTP response is built, the SDK returns nil resp; + // the wrapper must still produce a usable message anchored to the query window. + qp := &query.Params{Since: "2026-05-13T00:00:00Z"} + inner := errors.New("dial tcp: connection refused") + + wrapped := wrapListEventsError(qp, nil, inner) + msg := wrapped.Error() + require.Contains(t, msg, "okta-connectorv2: ListEvents failed") + require.NotContains(t, msg, "status=") + require.NotContains(t, msg, "request_id=") + require.Contains(t, msg, "since=2026-05-13T00:00:00Z") + require.NotContains(t, msg, "after=", "after= must be omitted when not set") + require.ErrorIs(t, wrapped, inner) +} + +func TestWrapListEventsError_MissingRequestIdAndCursor(t *testing.T) { + // Older Okta deployments or gateway-injected error responses sometimes omit the + // request-id header; first-page requests have no cursor. Optional fields must be + // elided cleanly (no empty key=value pairs). + resp := &okta.Response{Response: &http.Response{StatusCode: 500, Header: http.Header{}}} + qp := &query.Params{Since: "2026-05-13T00:00:00Z"} + inner := errors.New("the API returned an unknown error") + + wrapped := wrapListEventsError(qp, resp, inner) + msg := wrapped.Error() + require.Contains(t, msg, "status=500") + require.NotContains(t, msg, "request_id=") + require.NotContains(t, msg, "after=") +} From 89d813d0fa25fe4739a036c292a8c46e48111f46 Mon Sep 17 00:00:00 2001 From: arreyder Date: Thu, 14 May 2026 09:52:30 -0500 Subject: [PATCH 2/2] test(events): use net/http status constants in fixture (lint usestdlibvars) CI lint (usestdlibvars) flagged the literal `502` and `500` HTTP status codes in the wrap-error test fixtures; replace with `http.StatusBadGateway` and `http.StatusInternalServerError`. Behavior unchanged. Refs OPS-1539 Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/connector/event_log_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/event_log_test.go b/pkg/connector/event_log_test.go index c9485a01f..6ffd07a71 100644 --- a/pkg/connector/event_log_test.go +++ b/pkg/connector/event_log_test.go @@ -16,7 +16,7 @@ func TestWrapListEventsError_FullContext(t *testing.T) { // useless "the API returned an unknown error" sentinel from okta-sdk-golang. header := http.Header{} header.Set("X-Okta-Request-Id", "req-abc123") - resp := &okta.Response{Response: &http.Response{StatusCode: 502, Header: header}} + resp := &okta.Response{Response: &http.Response{StatusCode: http.StatusBadGateway, Header: header}} qp := &query.Params{Since: "2026-05-13T00:00:00Z", After: "0oa1cursor"} inner := errors.New("the API returned an unknown error") @@ -51,7 +51,7 @@ func TestWrapListEventsError_MissingRequestIdAndCursor(t *testing.T) { // Older Okta deployments or gateway-injected error responses sometimes omit the // request-id header; first-page requests have no cursor. Optional fields must be // elided cleanly (no empty key=value pairs). - resp := &okta.Response{Response: &http.Response{StatusCode: 500, Header: http.Header{}}} + resp := &okta.Response{Response: &http.Response{StatusCode: http.StatusInternalServerError, Header: http.Header{}}} qp := &query.Params{Since: "2026-05-13T00:00:00Z"} inner := errors.New("the API returned an unknown error")