@@ -8,8 +8,10 @@ import (
88 "net/http/httptest"
99 "runtime"
1010 "strings"
11+ "sync/atomic"
1112 "syscall"
1213 "testing"
14+ "time"
1315
1416 "github.com/google/go-cmp/cmp"
1517 "github.com/google/go-cmp/cmp/cmpopts"
@@ -18,6 +20,8 @@ import (
1820 "github.com/buildkite/agent/v3/logger"
1921)
2022
23+ var noSleep = WithRetrySleepFunc (func (time.Duration ) {})
24+
2125func TestFetchSecrets_Success (t * testing.T ) {
2226 t .Parallel ()
2327
@@ -44,7 +48,7 @@ func TestFetchSecrets_Success(t *testing.T) {
4448 Token : "llamas" ,
4549 })
4650
47- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , []string {"DATABASE_URL" , "API_TOKEN" }, 10 )
51+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , []string {"DATABASE_URL" , "API_TOKEN" }, 10 )
4852 if len (errs ) > 0 {
4953 t .Fatalf ("expected no errors, got: %v" , errs )
5054 }
@@ -73,7 +77,7 @@ func TestFetchSecrets_EmptyKeys(t *testing.T) {
7377 t .Cleanup (server .Close )
7478
7579 apiClient := api .NewClient (logger .Discard , api.Config {Endpoint : server .URL , Token : "llamas" })
76- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , []string {}, 10 )
80+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , []string {}, 10 )
7781
7882 if len (errs ) > 0 {
7983 t .Fatalf ("expected no errors, got: %v" , errs )
@@ -95,7 +99,7 @@ func TestFetchSecrets_NilKeys(t *testing.T) {
9599
96100 apiClient := api .NewClient (logger .Discard , api.Config {Endpoint : server .URL , Token : "llamas" })
97101
98- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , nil , 10 )
102+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , nil , 10 )
99103
100104 if len (errs ) > 0 {
101105 t .Fatalf ("expected no errors, got: %v" , errs )
@@ -133,7 +137,7 @@ func TestFetchSecrets_SomeSecretsFail(t *testing.T) {
133137 })
134138
135139 keys := []string {"DATABASE_URL" , "MISSING" }
136- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , keys , 10 )
140+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , keys , 10 )
137141
138142 if len (errs ) != 1 {
139143 t .Fatalf ("expected 1 errors, got %d: %v" , len (errs ), errs )
@@ -177,7 +181,7 @@ func TestFetchSecrets_AllSecretsFail(t *testing.T) {
177181 })
178182
179183 keys := []string {"API_TOKEN" , "DATABASE_URL" }
180- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , keys , 10 )
184+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , keys , 10 )
181185
182186 if len (errs ) != 2 {
183187 t .Fatalf ("expected 2 errors, got %d: %v" , len (errs ), errs )
@@ -218,7 +222,7 @@ func TestFetchSecrets_APIClientError(t *testing.T) {
218222 })
219223
220224 keys := []string {"TEST_SECRET" }
221- secrets , errs := FetchSecrets (t .Context (), apiClient , "test-job-id" , keys , 10 )
225+ secrets , errs := FetchSecrets (t .Context (), logger . Discard , apiClient , "test-job-id" , keys , 10 , noSleep )
222226
223227 if len (errs ) != 1 {
224228 t .Fatalf ("expected 1 error, got %d: %v" , len (errs ), errs )
@@ -266,3 +270,78 @@ func TestFetchSecrets_APIClientError(t *testing.T) {
266270 t .Errorf ("expected connection refused error, got: %v (type: %T)" , netErr .Err , netErr .Err )
267271 }
268272}
273+
274+ func TestFetchSecrets_RetriesOnServerError (t * testing.T ) {
275+ t .Parallel ()
276+
277+ var attempts atomic.Int32
278+
279+ server := httptest .NewServer (http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
280+ n := attempts .Add (1 )
281+ if n <= 2 {
282+ // First two attempts return 502 Bad Gateway (retryable)
283+ rw .WriteHeader (http .StatusBadGateway )
284+ _ , _ = fmt .Fprintf (rw , `{"message": "bad gateway"}` )
285+ return
286+ }
287+ // Third attempt succeeds
288+ rw .WriteHeader (http .StatusOK )
289+ _ , _ = fmt .Fprintf (rw , `{"key": "MY_SECRET", "value": "secret-value"}` )
290+ }))
291+ t .Cleanup (server .Close )
292+
293+ apiClient := api .NewClient (logger .Discard , api.Config {
294+ Endpoint : server .URL ,
295+ Token : "llamas" ,
296+ })
297+
298+ secrets , errs := FetchSecrets (t .Context (), logger .Discard , apiClient , "test-job-id" , []string {"MY_SECRET" }, 10 , noSleep )
299+ if len (errs ) > 0 {
300+ t .Fatalf ("expected no errors after retries, got: %v" , errs )
301+ }
302+
303+ if len (secrets ) != 1 {
304+ t .Fatalf ("expected 1 secret, got %d" , len (secrets ))
305+ }
306+
307+ if secrets [0 ].Value != "secret-value" {
308+ t .Errorf ("expected secret value %q, got %q" , "secret-value" , secrets [0 ].Value )
309+ }
310+
311+ if got := attempts .Load (); got != 3 {
312+ t .Errorf ("expected 3 attempts (2 failures + 1 success), got %d" , got )
313+ }
314+ }
315+
316+ func TestFetchSecrets_NoRetryOnNonRetryableStatus (t * testing.T ) {
317+ t .Parallel ()
318+
319+ var attempts atomic.Int32
320+
321+ server := httptest .NewServer (http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
322+ attempts .Add (1 )
323+ // 404 is not retryable
324+ rw .WriteHeader (http .StatusNotFound )
325+ _ , _ = fmt .Fprintf (rw , `{"message": "secret not found"}` )
326+ }))
327+ t .Cleanup (server .Close )
328+
329+ apiClient := api .NewClient (logger .Discard , api.Config {
330+ Endpoint : server .URL ,
331+ Token : "llamas" ,
332+ })
333+
334+ secrets , errs := FetchSecrets (t .Context (), logger .Discard , apiClient , "test-job-id" , []string {"MISSING" }, 10 , noSleep )
335+ if len (errs ) != 1 {
336+ t .Fatalf ("expected 1 error, got %d: %v" , len (errs ), errs )
337+ }
338+
339+ if secrets != nil {
340+ t .Errorf ("expected nil secrets, got: %v" , secrets )
341+ }
342+
343+ // Should have only attempted once since 404 is not retryable
344+ if got := attempts .Load (); got != 1 {
345+ t .Errorf ("expected 1 attempt (no retries for 404), got %d" , got )
346+ }
347+ }
0 commit comments