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
11 changes: 8 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,14 @@ func rootPersistentPreRun(deps *dependencies.Dependencies) func(cmd *cobra.Comma
}

explicitOutputSet := cmd.Flags().Changed("output")
signals, err := deps.DetectRuntimeSignals()
if err != nil {
return printRootPreRunError(cmd, err)
// An explicit --output wins over RENDER_OUTPUT, so the environment is
// only read (and validated) when it would actually select the format.
var signals command.RuntimeSignals
if !explicitOutputSet {
signals, err = deps.DetectRuntimeSignals()
if err != nil {
return printRootPreRunError(cmd, err)
}
}
output, err := command.ResolveAutoOutput(explicitOutputSet, requestedOutput, signals)
if err != nil {
Expand Down
83 changes: 83 additions & 0 deletions cmd/workspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,86 @@ func TestWorkspaces_NonInteractive_EmptyList(t *testing.T) {
assert.NotContains(t, result.Stdout, "@", "expected no workspace rows — all workspace emails contain @")
assert.True(t, server.HasRequest("GET", "/owners"), "expected GET /owners to be called")
}

// TestWorkspaces_ExplicitOutputOverridesInvalidRenderOutput runs the real root
// setup with the real runtime-signal detector, so RENDER_OUTPUT is read from the
// environment exactly as in a shipped binary.
func TestWorkspaces_ExplicitOutputOverridesInvalidRenderOutput(t *testing.T) {
testCases := []struct {
name string
args []string
wantErr string
wantRequest bool
wantStdout string
}{
{
name: "explicit json wins over the invalid default",
args: []string{"workspaces", "--output", "json"},
wantRequest: true,
wantStdout: `"name": "acme-corp"`,
},
{
name: "explicit yaml wins over the invalid default",
args: []string{"workspaces", "-o", "yaml"},
wantRequest: true,
wantStdout: "name: acme-corp",
},
{
name: "explicit text wins over the invalid default",
args: []string{"workspaces", "-o", "text"},
wantRequest: true,
wantStdout: "acme-corp",
},
{
name: "invalid default is still refused when selected",
args: []string{"workspaces"},
wantErr: "invalid RENDER_OUTPUT value: qa-invalid-output",
},
{
name: "invalid explicit flag is still refused",
args: []string{"workspaces", "-o", "xml"},
wantErr: "invalid output format: xml",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := renderapi.NewServer(t)
server.Owners.Add(renderapi.NewOwner(client.Owner{Name: "acme-corp", Email: "admin@acme.com"}))
t.Setenv("RENDER_CLI_CONFIG_PATH", newTestConfigPath(t))
t.Setenv("RENDER_HOST", server.URL())
t.Setenv("RENDER_API_KEY", "test-api-key")
t.Setenv("RENDER_WORKSPACE", "")
t.Setenv("RENDER_OUTPUT", "qa-invalid-output")

c, err := client.NewClientWithResponses(server.URL())
require.NoError(t, err)
deps := dependencies.New(c)

root := newRootCmd()
// A fresh copy keeps an earlier case's merged --output state from
// leaking into this one.
cmd := *workspacesCmd
cmd.ResetFlags()
root.AddCommand(&cmd)
setupRootCmdPersistentRun(root, deps)

var stdout, stderr bytes.Buffer
root.SetOut(&stdout)
root.SetErr(&stderr)
root.SetArgs(tc.args)

execErr := root.Execute()

if tc.wantErr != "" {
require.EqualError(t, execErr, tc.wantErr)
assert.Contains(t, stderr.String(), tc.wantErr)
assert.Empty(t, stdout.String())
} else {
require.NoError(t, execErr)
assert.Contains(t, stdout.String(), tc.wantStdout)
}
assert.Equal(t, tc.wantRequest, server.HasRequest("GET", "/owners"))
})
}
}