diff --git a/.changes/unreleased/BUG FIXES-20260713-133903.yaml b/.changes/unreleased/BUG FIXES-20260713-133903.yaml new file mode 100644 index 0000000..e7c1765 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20260713-133903.yaml @@ -0,0 +1,3 @@ +kind: BUG FIXES +body: Fix `api --all` returning no output when the result fits in a single page. The response body was consumed while checking for additional pages and not restored, so single-page responses rendered empty. +time: 2026-07-13T13:39:03-04:00 diff --git a/internal/commands/api/api.go b/internal/commands/api/api.go index 0b7c129..4a81c80 100644 --- a/internal/commands/api/api.go +++ b/internal/commands/api/api.go @@ -694,7 +694,10 @@ func paginateResponse(ctx context.Context, apiClient *client.Client, initial *ht combined, nextURL, err := parsePaginationPayload(initialBody) if err != nil || nextURL == nil { - + // io.ReadAll above consumed initial.Body. There is nothing to paginate + // (single page, or the payload isn't a paginated collection), so restore + // the body for the caller to read. + initial.Body = io.NopCloser(bytes.NewReader(initialBody)) return initial, err } diff --git a/internal/commands/api/api_paginate_singlepage_test.go b/internal/commands/api/api_paginate_singlepage_test.go new file mode 100644 index 0000000..a1c4aea --- /dev/null +++ b/internal/commands/api/api_paginate_singlepage_test.go @@ -0,0 +1,47 @@ +// Copyright IBM Corp. 2026 +// SPDX-License-Identifier: MPL-2.0 + +package api + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/tfctl-cli/internal/pkg/iostreams" +) + +// TestRunAPI_PaginateSinglePage covers the case where --all is set but the +// result fits in a single page (no "next" link). The response body must still +// be rendered; a single-page result must not come back empty just because +// pagination was requested. +func TestRunAPI_PaginateSinglePage(t *testing.T) { + t.Parallel() + + server, recorder := newAPITestServer(map[string]http.HandlerFunc{ + "GET /api/v2/workspaces": func(w http.ResponseWriter, _ *http.Request) { + writeJSONAPIResponse(w, http.StatusOK, map[string]any{ + "data": []any{ + map[string]any{"id": "ws-1", "type": "workspaces", "attributes": map[string]any{"name": "alpha"}}, + }, + "links": map[string]any{"next": nil}, + "meta": map[string]any{"pagination": map[string]any{"total-count": 1}}, + }) + }, + }) + defer server.Close() + + io := iostreams.Test() + err := RunAPI(context.Background(), newTestOpts(t, server.URL, io, func(opts *Opts) { + opts.URL = mustResolveTestURL(t, opts.Client.BaseURL.String(), "/workspaces") + opts.All = true + })) + require.NoError(t, err) + + require.Len(t, recorder.All(), 1) + require.Contains(t, io.Output.String(), "alpha", + "single-page --all result must still be rendered, got: %q", io.Output.String()) + require.Empty(t, io.Error.String()) +}