Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion pkg/connector/event_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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)
}
63 changes: 63 additions & 0 deletions pkg/connector/event_log_test.go
Original file line number Diff line number Diff line change
@@ -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: http.StatusBadGateway, 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: http.StatusInternalServerError, 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=")
}
Loading