1package env
2
3import (
4 "context"
5 "os/exec"
6 "testing"
7 "time"
8
9 "github.com/stretchr/testify/assert"
10)
11
12func TestNewPassProvider(t *testing.T) {
13 provider := NewPassProvider()
14 assert.NotNil(t, provider)
15 assert.Equal(t, "pass", provider.Name())
16 assert.Equal(t, 10*time.Second, provider.timeout)
17}
18
19func TestPassProvider_Name(t *testing.T) {
20 provider := NewPassProvider()
21 assert.Equal(t, "pass", provider.Name())
22}
23
24func TestPassProvider_GetSecret(t *testing.T) {
25 provider := NewPassProvider()
26
27 tests := []struct {
28 name string
29 path string
30 mockCommand bool
31 mockOutput string
32 mockError bool
33 expectedSecret string
34 expectedError bool
35 }{
36 {
37 name: "successful secret retrieval",
38 path: "test/secret",
39 mockCommand: true,
40 mockOutput: "secret-value\n",
41 expectedSecret: "secret-value",
42 },
43 {
44 name: "command execution fails",
45 path: "test/nonexistent",
46 mockCommand: true,
47 mockError: true,
48 expectedError: true,
49 },
50 {
51 name: "empty secret returned",
52 path: "test/empty",
53 mockCommand: true,
54 mockOutput: "\n",
55 expectedError: true,
56 },
57 }
58
59 for _, tt := range tests {
60 t.Run(tt.name, func(t *testing.T) {
61 // Check if pass command is available
62 _, err := exec.LookPath("pass")
63 if err != nil {
64 t.Skip("pass command not available, skipping test")
65 }
66
67 // Since we can't easily mock exec.Command in this context,
68 // we'll test the interface and structure, but skip actual execution
69 // unless we have a real pass installation
70
71 // Test the provider structure
72 assert.Equal(t, "pass", provider.Name())
73 assert.NotZero(t, provider.timeout)
74
75 // For actual testing, we would need a more sophisticated mocking approach
76 // or integration tests with a real pass setup
77 if !tt.mockCommand {
78 secret, err := provider.GetSecret(tt.path)
79 if tt.expectedError {
80 assert.Error(t, err)
81 } else {
82 assert.NoError(t, err)
83 assert.Equal(t, tt.expectedSecret, secret)
84 }
85 }
86 })
87 }
88}
89
90func TestNewSopsProvider(t *testing.T) {
91 provider := NewSopsProvider()
92 assert.NotNil(t, provider)
93 assert.Equal(t, "sops", provider.Name())
94 assert.Equal(t, 10*time.Second, provider.timeout)
95}
96
97func TestSopsProvider_Name(t *testing.T) {
98 provider := NewSopsProvider()
99 assert.Equal(t, "sops", provider.Name())
100}
101
102func TestSopsProvider_GetSecret(t *testing.T) {
103 provider := NewSopsProvider()
104
105 tests := []struct {
106 name string
107 path string
108 expectedSecret string
109 expectedError bool
110 }{
111 {
112 name: "invalid path format - no colon",
113 path: "invalid-path",
114 expectedError: true,
115 },
116 {
117 name: "invalid path format - empty key",
118 path: "file.yaml:",
119 expectedError: true,
120 },
121 {
122 name: "invalid path format - empty file",
123 path: ":key.path",
124 expectedError: true,
125 },
126 }
127
128 for _, tt := range tests {
129 t.Run(tt.name, func(t *testing.T) {
130 // Check if sops command is available
131 _, err := exec.LookPath("sops")
132 if err != nil {
133 t.Skip("sops command not available, skipping test")
134 }
135
136 // Test the provider structure
137 assert.Equal(t, "sops", provider.Name())
138 assert.NotZero(t, provider.timeout)
139
140 // Test path validation
141 secret, err := provider.GetSecret(tt.path)
142 if tt.expectedError {
143 assert.Error(t, err)
144 assert.Empty(t, secret)
145 } else {
146 // For successful cases, we'd need actual sops files
147 // which is complex to set up in unit tests
148 // This would be better tested in integration tests
149 }
150 })
151 }
152}
153
154func TestSopsProvider_GetSecret_ValidFormat(t *testing.T) {
155 provider := NewSopsProvider()
156
157 // Check if sops command is available
158 _, err := exec.LookPath("sops")
159 if err != nil {
160 t.Skip("sops command not available, skipping test")
161 }
162
163 // Test with valid format but non-existent file (should fail gracefully)
164 _, err = provider.GetSecret("nonexistent.yaml:some.key")
165 assert.Error(t, err)
166 assert.Contains(t, err.Error(), "sops command failed")
167}
168
169func TestSecretProvider_Interface(t *testing.T) {
170 // Test that our providers implement the SecretProvider interface
171 var _ SecretProvider = NewPassProvider()
172 var _ SecretProvider = NewSopsProvider()
173}
174
175func TestProviders_Timeout(t *testing.T) {
176 // Test that providers have reasonable timeouts
177 passProvider := NewPassProvider()
178 sopsProvider := NewSopsProvider()
179
180 assert.Equal(t, 10*time.Second, passProvider.timeout)
181 assert.Equal(t, 10*time.Second, sopsProvider.timeout)
182}
183
184func TestProviders_ContextCancellation(t *testing.T) {
185 // Test that providers respect context cancellation
186 passProvider := NewPassProvider()
187 sopsProvider := NewSopsProvider()
188
189 // Create a context that's already cancelled
190 _, cancel := context.WithCancel(context.Background())
191 cancel()
192
193 // Providers should handle cancelled contexts gracefully
194 // (This is more of a behavioral test - the actual implementation
195 // uses exec.CommandContext which should respect cancellation)
196 assert.NotNil(t, passProvider)
197 assert.NotNil(t, sopsProvider)
198}
199
200// Mock implementations for more controlled testing
201type MockPassProvider struct {
202 output string
203 err error
204}
205
206func (m *MockPassProvider) Name() string {
207 return "pass"
208}
209
210func (m *MockPassProvider) GetSecret(path string) (string, error) {
211 if m.err != nil {
212 return "", m.err
213 }
214 return m.output, nil
215}
216
217type MockSopsProvider struct {
218 output string
219 err error
220}
221
222func (m *MockSopsProvider) Name() string {
223 return "sops"
224}
225
226func (m *MockSopsProvider) GetSecret(path string) (string, error) {
227 if m.err != nil {
228 return "", m.err
229 }
230 return m.output, nil
231}
232
233func TestMockProviders(t *testing.T) {
234 // Test our mock implementations
235 mockPass := &MockPassProvider{output: "test-secret", err: nil}
236 mockSops := &MockSopsProvider{output: "sops-secret", err: nil}
237
238 assert.Equal(t, "pass", mockPass.Name())
239 assert.Equal(t, "sops", mockSops.Name())
240
241 secret, err := mockPass.GetSecret("test/path")
242 assert.NoError(t, err)
243 assert.Equal(t, "test-secret", secret)
244
245 secret, err = mockSops.GetSecret("file.yaml:key")
246 assert.NoError(t, err)
247 assert.Equal(t, "sops-secret", secret)
248}