@@ -5,8 +5,15 @@ import (
55 "path/filepath"
66 "testing"
77
8+ "github.com/spf13/cobra"
9+ "github.com/spf13/viper"
810 "github.com/stretchr/testify/assert"
911 "github.com/stretchr/testify/require"
12+ "go.uber.org/mock/gomock"
13+
14+ "github.com/cloudposse/atmos/internal/exec"
15+ "github.com/cloudposse/atmos/pkg/schema"
16+ "github.com/cloudposse/atmos/pkg/store"
1017)
1118
1219func TestDescribeComponentCmd_Error (t * testing.T ) {
@@ -27,6 +34,164 @@ func TestDescribeComponentCmd_ProvenanceFlag(t *testing.T) {
2734 assert .Equal (t , "false" , provenanceFlag .DefValue , "provenance flag should default to false" )
2835}
2936
37+ func TestHasIdentityBackedStore (t * testing.T ) {
38+ ctrl := gomock .NewController (t )
39+ identityAware := store .NewMockIdentityAwareStore (ctrl )
40+ plain := store .NewMockStore (ctrl )
41+
42+ tests := []struct {
43+ name string
44+ atmosConfig * schema.AtmosConfiguration
45+ want bool
46+ }{
47+ {name : "nil configuration" , atmosConfig : nil , want : false },
48+ {
49+ name : "plain store with identity" ,
50+ atmosConfig : & schema.AtmosConfiguration {
51+ StoresConfig : store.StoresConfig {"plain" : {Identity : "platform" }},
52+ Stores : store.StoreRegistry {"plain" : plain },
53+ },
54+ want : false ,
55+ },
56+ {
57+ name : "identity-aware store without identity" ,
58+ atmosConfig : & schema.AtmosConfiguration {
59+ StoresConfig : store.StoresConfig {"cloud" : {}},
60+ Stores : store.StoreRegistry {"cloud" : identityAware },
61+ },
62+ want : false ,
63+ },
64+ {
65+ name : "identity-aware store with identity" ,
66+ atmosConfig : & schema.AtmosConfiguration {
67+ StoresConfig : store.StoresConfig {"cloud" : {Identity : "platform" }},
68+ Stores : store.StoreRegistry {"cloud" : identityAware },
69+ },
70+ want : true ,
71+ },
72+ }
73+ for _ , tt := range tests {
74+ t .Run (tt .name , func (t * testing.T ) {
75+ assert .Equal (t , tt .want , hasIdentityBackedStore (tt .atmosConfig ))
76+ })
77+ }
78+ }
79+
80+ // TestGetRunnableDescribeComponentCmd_InvalidErrorMode covers the dispatch call site
81+ // inside getRunnableDescribeComponentCmd that rejects a resolved --error-mode value that
82+ // isn't one of "strict", "warn", or "silent" once resolved against atmos.yaml's
83+ // describe.error_mode: an invalid resolved value must short-circuit before the describe
84+ // component executor ever runs. Mirrors describe_stacks_test.go's and
85+ // describe_dependents_test.go's InvalidErrorMode tests for the same shared --error-mode
86+ // flag resolution path (cmd/describe_error_mode_flag.go).
87+ //
88+ // Unlike those siblings, the value is set via ParseFlags rather than by reaching into the
89+ // registered flag's Value directly, since describeComponentCmd's --error-mode is a
90+ // PersistentFlag, and cobra only merges persistent flags into the command's own flag set
91+ // on the first ParseFlags/Execute call, not on registration. Its siblings happen to get
92+ // that merge for free from an unrelated earlier test's real dispatch call, but
93+ // describeComponentCmd does not, so looking up the flag directly would return nil here
94+ // depending on test order. ParseFlags both triggers the merge and sets the value in one
95+ // deterministic step.
96+ func TestGetRunnableDescribeComponentCmd_InvalidErrorMode (t * testing.T ) {
97+ tk := NewTestKit (t )
98+
99+ viper .Reset ()
100+ tk .Setenv ("ATMOS_IDENTITY" , "" )
101+ tk .Setenv ("IDENTITY" , "" )
102+
103+ errorModeFlag := describeComponentCmd .PersistentFlags ().Lookup (describeErrorModeFlagName )
104+ require .NotNil (t , errorModeFlag , "error-mode flag must be registered on describeComponentCmd" )
105+ origValue := errorModeFlag .Value .String ()
106+ origChanged := errorModeFlag .Changed
107+ t .Cleanup (func () {
108+ _ = errorModeFlag .Value .Set (origValue )
109+ errorModeFlag .Changed = origChanged
110+ })
111+ require .NoError (t , describeComponentCmd .ParseFlags ([]string {"--error-mode=bogus" }))
112+
113+ ctrl := gomock .NewController (t )
114+ defer ctrl .Finish ()
115+
116+ mockExec := exec .NewMockDescribeComponentCmdExec (ctrl )
117+ mockExec .EXPECT ().ExecuteDescribeComponentCmd (gomock .Any ()).Times (0 )
118+
119+ run := getRunnableDescribeComponentCmd (getRunnableDescribeComponentCmdProps {
120+ checkAtmosConfigE : func (opts ... AtmosValidateOption ) error { return nil },
121+ initCliConfig : func (info schema.ConfigAndStacksInfo , processStacks bool ) (schema.AtmosConfiguration , error ) {
122+ return schema.AtmosConfiguration {}, nil
123+ },
124+ isExplicitComponentPath : func (component string ) bool { return false },
125+ resolveComponentFromPath : func (atmosConfig * schema.AtmosConfiguration , component , stack string ) (string , error ) {
126+ return component , nil
127+ },
128+ executeDescribeComponent : func (params * exec.ExecuteDescribeComponentParams ) (map [string ]any , error ) {
129+ return nil , nil
130+ },
131+ newDescribeComponentExec : mockExec ,
132+ })
133+
134+ err := run (describeComponentCmd , []string {"vpc" })
135+
136+ require .ErrorIs (t , err , exec .ErrInvalidErrorMode , "invalid error-mode should be rejected before executing" )
137+ }
138+
139+ // TestGetRunnableDescribeComponentCmd_ErrorModeWrongType covers the genuinely-forceable
140+ // return-err branch on cmd.Flags().GetString(describeErrorModeFlagName) inside
141+ // getRunnableDescribeComponentCmd: registering "error-mode" as a Bool flag (instead of the
142+ // real String flag) reproduces a type mismatch without needing to touch any
143+ // BindFlagsToViper-adjacent code path. Mirrors describe_dependents_test.go's
144+ // TestSetFlagsForDescribeDependentsCmd_ErrorModeWrongType and
145+ // describe_edition_test.go's TestDescribeEditionCmd_FormatFlagWrongType.
146+ //
147+ // Note: resolveDescribeErrorModeFlag itself still succeeds here (binding a Bool pflag to
148+ // Viper doesn't error, and Viper's GetString on the bound Bool value round-trips to
149+ // "false", which cmd.Flags().Set("error-mode", "false") happily accepts on a Bool flag)
150+ // -- it's the subsequent cmd.Flags().GetString call that fails, because the flag is
151+ // genuinely a Bool.
152+ func TestGetRunnableDescribeComponentCmd_ErrorModeWrongType (t * testing.T ) {
153+ tk := NewTestKit (t )
154+ viper .Reset ()
155+
156+ testCmd := & cobra.Command {Use : "component" }
157+ testCmd .Flags ().String ("stack" , "" , "" )
158+ testCmd .Flags ().String ("format" , "yaml" , "" )
159+ testCmd .Flags ().String ("file" , "" , "" )
160+ testCmd .Flags ().Bool ("process-templates" , true , "" )
161+ testCmd .Flags ().Bool ("process-functions" , true , "" )
162+ testCmd .Flags ().String ("query" , "" , "" )
163+ testCmd .Flags ().StringSlice ("skip" , nil , "" )
164+ testCmd .Flags ().Bool ("provenance" , false , "" )
165+ testCmd .Flags ().Bool ("error-mode" , false , "" )
166+ require .NoError (t , testCmd .Flags ().Set ("error-mode" , "true" ))
167+
168+ ctrl := gomock .NewController (t )
169+ defer ctrl .Finish ()
170+
171+ mockExec := exec .NewMockDescribeComponentCmdExec (ctrl )
172+ mockExec .EXPECT ().ExecuteDescribeComponentCmd (gomock .Any ()).Times (0 )
173+
174+ run := getRunnableDescribeComponentCmd (getRunnableDescribeComponentCmdProps {
175+ checkAtmosConfigE : func (opts ... AtmosValidateOption ) error { return nil },
176+ initCliConfig : func (info schema.ConfigAndStacksInfo , processStacks bool ) (schema.AtmosConfiguration , error ) {
177+ return schema.AtmosConfiguration {}, nil
178+ },
179+ isExplicitComponentPath : func (component string ) bool { return false },
180+ resolveComponentFromPath : func (atmosConfig * schema.AtmosConfiguration , component , stack string ) (string , error ) {
181+ return component , nil
182+ },
183+ executeDescribeComponent : func (params * exec.ExecuteDescribeComponentParams ) (map [string ]any , error ) {
184+ return nil , nil
185+ },
186+ newDescribeComponentExec : mockExec ,
187+ })
188+
189+ err := run (testCmd , []string {"vpc" })
190+
191+ require .Error (tk , err , "GetString on a Bool-typed error-mode flag must return an error" )
192+ assert .NotErrorIs (tk , err , exec .ErrInvalidErrorMode , "the failure must come from GetString, not error-mode validation" )
193+ }
194+
30195// TestDescribeComponentCmd_ProvenanceWithFormatJSON tests that provenance and format flags
31196// are correctly parsed and accepted. This is a flag parsing test, not a functional test.
32197func TestDescribeComponentCmd_ProvenanceWithFormatJSON (t * testing.T ) {
0 commit comments