From 0cfbaf64a64d64a3fe7c63932a0c4e39758541a8 Mon Sep 17 00:00:00 2001 From: Jordan English <6087717+jordanenglish@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:39:26 -0400 Subject: [PATCH] api: restore response body on single-page --all paginateResponse read the initial response body to check for additional pages but did not restore it when there were none (or the payload was not a paginated collection). The drained body then failed to render, so `api --all` returned no output on single-page results. Restore the buffered body before returning on that path. Adds a test covering --all against a single-page response. --- .../unreleased/BUG FIXES-20260713-133903.yaml | 3 ++ internal/commands/api/api.go | 5 +- .../api/api_paginate_singlepage_test.go | 47 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/BUG FIXES-20260713-133903.yaml create mode 100644 internal/commands/api/api_paginate_singlepage_test.go 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()) +}