diff --git a/pkg/accessreview/drivers/upcloud.go b/pkg/accessreview/drivers/upcloud.go index af5487adb..83e6b26c5 100644 --- a/pkg/accessreview/drivers/upcloud.go +++ b/pkg/accessreview/drivers/upcloud.go @@ -126,6 +126,10 @@ func (d *UpCloudDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro details, err := d.fetchAccountDetails(ctx, username) if err != nil { + if ctx.Err() != nil { + return nil, fmt.Errorf("cannot list upcloud accounts: %w", ctx.Err()) + } + d.logger.WarnCtx(ctx, "cannot fetch upcloud account details, using list fields only", log.Error(err)) } else { if name := strings.TrimSpace(details.FirstName + " " + details.LastName); name != "" { diff --git a/pkg/accessreview/drivers/upcloud_test.go b/pkg/accessreview/drivers/upcloud_test.go index a871d391d..30f45a86c 100644 --- a/pkg/accessreview/drivers/upcloud_test.go +++ b/pkg/accessreview/drivers/upcloud_test.go @@ -22,7 +22,11 @@ package drivers import ( "context" + "errors" + "io" + "net/http" "os" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -77,3 +81,44 @@ func TestUpCloudDriver(t *testing.T) { assert.Equal(t, []string{"billing"}, billing.Roles) assert.False(t, billing.IsAdmin) } + +// upcloudRoundTripFunc adapts a function to http.RoundTripper. +type upcloudRoundTripFunc func(*http.Request) (*http.Response, error) + +func (f upcloudRoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + +// TestUpCloudDriverContextCancellation verifies that a context canceled +// mid-run aborts ListAccounts with the cancellation error instead of being +// swallowed as a best-effort per-account detail failure, which would let a +// caller mistake a truncated run for a complete, successful sync. +func TestUpCloudDriverContextCancellation(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + + client := &http.Client{ + Transport: upcloudRoundTripFunc(func(req *http.Request) (*http.Response, error) { + if strings.Contains(req.URL.Path, "/account/details/") { + cancel() + + return nil, ctx.Err() + } + + body := `{"accounts":{"account":[{"labels":[],"roles":{"role":["technical"]},"type":"mymain","username":"test"}]}}` + + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(body)), + Header: http.Header{"Content-Type": []string{"application/json"}}, + }, nil + }), + } + + driver := NewUpCloudDriver(client, log.NewLogger(log.WithName("test"))) + records, err := driver.ListAccounts(ctx) + require.Error(t, err) + assert.True(t, errors.Is(err, context.Canceled)) + assert.Nil(t, records) +}