-
-
Notifications
You must be signed in to change notification settings - Fork 151
Expand file tree
/
Copy pathauth_exec_test.go
More file actions
330 lines (307 loc) · 10 KB
/
auth_exec_test.go
File metadata and controls
330 lines (307 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
package cmd
import (
"bytes"
"errors"
"os"
"runtime"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
errUtils "github.com/cloudposse/atmos/errors"
"github.com/cloudposse/atmos/pkg/data"
iolib "github.com/cloudposse/atmos/pkg/io"
"github.com/cloudposse/atmos/pkg/ui"
)
func TestAuthExecCmd_FlagParsing(t *testing.T) {
// Set up test fixture with auth configuration.
testDir := "../tests/fixtures/scenarios/atmos-auth"
t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir)
t.Setenv("ATMOS_BASE_PATH", testDir)
tests := []struct {
name string
args []string
skipOnWindows bool
expectedError string
}{
{
name: "identity flag without command",
args: []string{"--identity=test-user"},
expectedError: "no command specified",
},
{
name: "double dash without command",
args: []string{"--"},
expectedError: "no command specified",
},
{
name: "identity flag with double dash but no command",
args: []string{"--identity=test-user", "--"},
expectedError: "no command specified",
},
{
name: "nonexistent identity",
args: []string{"--identity=nonexistent", "--", "echo", "test"},
expectedError: "identity not found",
},
{
name: "identity flag with no value before double dash",
args: []string{"--identity", "--", "echo", "test"},
expectedError: "requires a TTY", // Interactive selection requires TTY.
},
{
name: "valid command with default identity",
args: []string{"echo", "test"},
// This will fail with auth errors since we don't have real AWS SSO configured.
expectedError: "authentication failed",
},
{
name: "valid command with specific identity and double dash",
args: []string{"--identity=test-user", "--", "echo", "hello"},
skipOnWindows: true, // echo behaves differently on Windows
// This will fail with auth errors since we don't have real AWS credentials.
expectedError: "authentication failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.skipOnWindows && runtime.GOOS == "windows" {
t.Skipf("Skipping test on Windows: echo command behaves differently")
}
// Create a command instance with the same flags as the real authExecCmd.
testCmd := &cobra.Command{
Use: "exec",
DisableFlagParsing: true,
}
testCmd.Flags().AddFlagSet(authExecCmd.Flags())
// Capture output.
var buf bytes.Buffer
testCmd.SetOut(&buf)
testCmd.SetErr(&buf)
// Call the core business logic directly, bypassing handleHelpRequest and checkAtmosConfig.
err := executeAuthExecCommandCore(testCmd, tt.args)
if tt.expectedError != "" {
assert.Error(t, err)
if err != nil {
assert.Contains(t, err.Error(), tt.expectedError)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthExecCmd_CommandStructure(t *testing.T) {
// Test that the real authExecCmd has the expected structure.
assert.Equal(t, "exec", authExecCmd.Use)
assert.True(t, authExecCmd.DisableFlagParsing, "DisableFlagParsing should be true to allow pass-through of command flags")
// Verify identity flag exists (inherited from parent authCmd).
identityFlag := authExecCmd.Flag("identity")
require.NotNil(t, identityFlag, "identity flag should be inherited from parent authCmd")
assert.Equal(t, "i", identityFlag.Shorthand)
assert.Equal(t, "", identityFlag.DefValue)
assert.Equal(t, IdentityFlagSelectValue, identityFlag.NoOptDefVal, "NoOptDefVal should be __SELECT__")
}
func TestExtractIdentityFlag(t *testing.T) {
tests := []struct {
name string
args []string
expectedIdentity string
expectedCommandArgs []string
}{
{
name: "no flags, just command",
args: []string{"echo", "hello"},
expectedIdentity: "",
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "identity with value, double dash, command",
args: []string{"--identity=test-user", "--", "echo", "hello"},
expectedIdentity: "test-user",
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "identity equals syntax",
args: []string{"--identity=test-user", "--", "echo", "hello"},
expectedIdentity: "test-user",
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "identity flag without value before double dash",
args: []string{"--identity", "--", "echo", "hello"},
expectedIdentity: IdentityFlagSelectValue,
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "identity flag without value, no double dash",
args: []string{"--identity", "echo", "hello"},
expectedIdentity: "echo",
expectedCommandArgs: []string{"hello"},
},
{
name: "short flag -i with value",
args: []string{"-i", "test-user", "--", "aws", "s3", "ls"},
expectedIdentity: "test-user",
expectedCommandArgs: []string{"aws", "s3", "ls"},
},
{
name: "short flag -i without value before double dash",
args: []string{"-i", "--", "aws", "s3", "ls"},
expectedIdentity: IdentityFlagSelectValue,
expectedCommandArgs: []string{"aws", "s3", "ls"},
},
{
name: "double dash with no identity flag",
args: []string{"--", "echo", "hello"},
expectedIdentity: "",
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "identity equals empty string",
args: []string{"--identity=", "--", "echo", "hello"},
expectedIdentity: IdentityFlagSelectValue,
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "no double dash, identity with value",
args: []string{"--identity=test-user", "terraform", "plan"},
expectedIdentity: "test-user",
expectedCommandArgs: []string{"terraform", "plan"},
},
{
name: "identity at end with no value",
args: []string{"echo", "hello", "--identity"},
expectedIdentity: IdentityFlagSelectValue,
expectedCommandArgs: []string{"echo", "hello"},
},
{
name: "empty args",
args: []string{},
expectedIdentity: "",
expectedCommandArgs: nil,
},
{
name: "only identity flag",
args: []string{"--identity"},
expectedIdentity: IdentityFlagSelectValue,
expectedCommandArgs: nil,
},
{
name: "only double dash",
args: []string{"--"},
expectedIdentity: "",
expectedCommandArgs: nil, // No args after "--"
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
identity, commandArgs := extractIdentityFlag(tt.args)
assert.Equal(t, tt.expectedIdentity, identity, "identity value mismatch")
assert.Equal(t, tt.expectedCommandArgs, commandArgs, "command args mismatch")
})
}
}
func TestExecuteCommandWithEnv(t *testing.T) {
// Use the test binary itself as a cross-platform subprocess helper.
// TestMain in testing_main_test.go handles _ATMOS_TEST_EXIT_ONE.
exePath, err := os.Executable()
require.NoError(t, err, "os.Executable() must succeed")
// Prerequisite: verify that env vars reach the child process.
t.Run("env propagation to subprocess", func(t *testing.T) {
// Running the test binary with -test.run=^$ matches no tests and exits 0,
// confirming the subprocess receives the provided environment.
err := executeCommandWithEnv(
[]string{exePath, "-test.run=^$"},
[]string{"TEST_VAR=test-value"},
)
assert.NoError(t, err)
})
// Test the command execution helper directly.
tests := []struct {
name string
args []string
envVars []string
expectedError string
expectedCode int // Expected exit code if error is ExitCodeError.
}{
{
name: "empty args",
args: []string{},
envVars: []string{},
expectedError: "no command specified",
},
{
name: "successful command",
args: []string{exePath, "-test.run=^$"},
envVars: []string{"TEST_VAR=test-value"},
},
{
name: "nonexistent command",
args: []string{"nonexistent-command-xyz"},
envVars: []string{},
expectedError: "command not found",
},
{
name: "command with non-zero exit code",
args: []string{exePath, "-test.run=^$"},
envVars: []string{"_ATMOS_TEST_EXIT_ONE=1"},
expectedCode: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := executeCommandWithEnv(tt.args, tt.envVars)
switch {
case tt.expectedError != "":
assert.Error(t, err)
if err != nil {
assert.Contains(t, err.Error(), tt.expectedError)
}
case tt.expectedCode != 0:
assert.Error(t, err)
// Check that it's an ExitCodeError with the correct code.
var exitCodeErr errUtils.ExitCodeError
if assert.True(t, errors.As(err, &exitCodeErr), "error should be ExitCodeError") {
assert.Equal(t, tt.expectedCode, exitCodeErr.Code)
}
default:
assert.NoError(t, err)
}
})
}
}
func TestPrintAuthExecTip(t *testing.T) {
tests := []struct {
name string
identityName string
}{
{
name: "shows tip with identity name",
identityName: "test-identity",
},
{
name: "shows tip with different identity name",
identityName: "dev-admin",
},
{
name: "handles empty identity name",
identityName: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Initialize I/O context for UI layer.
ioCtx, err := iolib.NewContext()
require.NoError(t, err)
data.InitWriter(ioCtx)
ui.InitFormatter(ioCtx)
// Call the function - it should not panic.
// The actual output formatting is tested by the UI layer tests.
// We verify the function executes without error with the identity name.
assert.NotPanics(t, func() {
printAuthExecTip(tt.identityName)
})
})
}
}