@@ -6,13 +6,21 @@ import (
66 "net/http"
77 "net/http/httptest"
88 "strings"
9+ "sync/atomic"
910 "testing"
1011 "time"
1112
1213 "github.com/stretchr/testify/assert"
1314 "github.com/stretchr/testify/require"
1415)
1516
17+ func init () {
18+ // Speed up all retry-related tests by disabling inter-attempt delays and
19+ // shortening the per-attempt HTTP timeout so timeout tests don't take 30 s.
20+ schemaFetchRetryDelay = 0
21+ schemaHTTPClientTimeout = 200 * time .Millisecond
22+ }
23+
1624// TestFetchAndFixSchema_SuccessfulFetch tests the happy path where schema is fetched successfully
1725func TestFetchAndFixSchema_SuccessfulFetch (t * testing.T ) {
1826 // Create a minimal valid schema for testing
@@ -52,35 +60,48 @@ func TestFetchAndFixSchema_SuccessfulFetch(t *testing.T) {
5260// TestFetchAndFixSchema_HTTPError tests handling of HTTP error responses
5361func TestFetchAndFixSchema_HTTPError (t * testing.T ) {
5462 tests := []struct {
55- name string
56- statusCode int
57- wantErr string
63+ name string
64+ statusCode int
65+ wantErr string
66+ wantRequests int // expected number of HTTP requests (1 = no retry, 3 = full retry)
5867 }{
5968 {
60- name : "404 Not Found" ,
61- statusCode : http .StatusNotFound ,
62- wantErr : "failed to fetch schema: HTTP 404" ,
69+ name : "404 Not Found" ,
70+ statusCode : http .StatusNotFound ,
71+ wantErr : "failed to fetch schema: HTTP 404" ,
72+ wantRequests : 1 , // permanent error, no retry
6373 },
6474 {
65- name : "500 Internal Server Error" ,
66- statusCode : http .StatusInternalServerError ,
67- wantErr : "failed to fetch schema: HTTP 500" ,
75+ name : "500 Internal Server Error" ,
76+ statusCode : http .StatusInternalServerError ,
77+ wantErr : "failed to fetch schema: HTTP 500" ,
78+ wantRequests : maxSchemaFetchRetries , // transient, retried
6879 },
6980 {
70- name : "403 Forbidden" ,
71- statusCode : http .StatusForbidden ,
72- wantErr : "failed to fetch schema: HTTP 403" ,
81+ name : "403 Forbidden" ,
82+ statusCode : http .StatusForbidden ,
83+ wantErr : "failed to fetch schema: HTTP 403" ,
84+ wantRequests : 1 , // permanent error, no retry
7385 },
7486 {
75- name : "503 Service Unavailable" ,
76- statusCode : http .StatusServiceUnavailable ,
77- wantErr : "failed to fetch schema: HTTP 503" ,
87+ name : "503 Service Unavailable" ,
88+ statusCode : http .StatusServiceUnavailable ,
89+ wantErr : "failed to fetch schema: HTTP 503" ,
90+ wantRequests : maxSchemaFetchRetries , // transient, retried
91+ },
92+ {
93+ name : "429 Too Many Requests" ,
94+ statusCode : http .StatusTooManyRequests ,
95+ wantErr : "failed to fetch schema: HTTP 429" ,
96+ wantRequests : maxSchemaFetchRetries , // transient, retried
7897 },
7998 }
8099
81100 for _ , tt := range tests {
82101 t .Run (tt .name , func (t * testing.T ) {
102+ var requestCount atomic.Int32
83103 server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
104+ requestCount .Add (1 )
84105 w .WriteHeader (tt .statusCode )
85106 }))
86107 defer server .Close ()
@@ -90,6 +111,8 @@ func TestFetchAndFixSchema_HTTPError(t *testing.T) {
90111 assert .Error (t , err )
91112 assert .Nil (t , result )
92113 assert .Contains (t , err .Error (), tt .wantErr )
114+ assert .Equal (t , int32 (tt .wantRequests ), requestCount .Load (),
115+ "expected %d HTTP request(s) for status %d" , tt .wantRequests , tt .statusCode )
93116 })
94117 }
95118}
@@ -108,9 +131,11 @@ func TestFetchAndFixSchema_NetworkError(t *testing.T) {
108131
109132// TestFetchAndFixSchema_Timeout tests handling of request timeouts
110133func TestFetchAndFixSchema_Timeout (t * testing.T ) {
111- // Create a server that delays longer than the client timeout
134+ // Create a server that delays longer than the configured client timeout.
135+ // The init() in this test file sets schemaHTTPClientTimeout = 200ms, so any
136+ // delay > 200ms will trigger a timeout on each attempt.
112137 server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
113- time .Sleep (15 * time .Second ) // fetchAndFixSchema has 10 second timeout
138+ time .Sleep (500 * time .Millisecond )
114139 w .WriteHeader (http .StatusOK )
115140 }))
116141 defer server .Close ()
@@ -589,3 +614,53 @@ func TestFetchAndFixSchema_LargeSchema(t *testing.T) {
589614 require .True (t , ok )
590615 assert .Equal (t , 100 , len (properties ), "Should preserve all 100 properties" )
591616}
617+
618+ // TestFetchAndFixSchema_RetrySucceedsAfterTransientError verifies that a transient
619+ // error on the first attempt is retried and the eventual success is returned.
620+ func TestFetchAndFixSchema_RetrySucceedsAfterTransientError (t * testing.T ) {
621+ validSchema := map [string ]interface {}{
622+ "$schema" : "http://json-schema.org/draft-07/schema#" ,
623+ "type" : "object" ,
624+ }
625+ schemaJSON , err := json .Marshal (validSchema )
626+ require .NoError (t , err )
627+
628+ var requestCount atomic.Int32
629+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
630+ n := requestCount .Add (1 )
631+ if n < 3 {
632+ // First two attempts return 429
633+ w .WriteHeader (http .StatusTooManyRequests )
634+ return
635+ }
636+ w .WriteHeader (http .StatusOK )
637+ w .Write (schemaJSON )
638+ }))
639+ defer server .Close ()
640+
641+ result , err := fetchAndFixSchema (server .URL )
642+
643+ require .NoError (t , err )
644+ assert .NotNil (t , result )
645+ assert .Equal (t , int32 (3 ), requestCount .Load (), "should have made 3 requests (2 failures + 1 success)" )
646+ }
647+
648+ // TestFetchAndFixSchema_ExponentialBackoffDelays verifies that all retries are attempted
649+ // when transient errors occur, and that the function eventually gives up after
650+ // maxSchemaFetchRetries attempts.
651+ func TestFetchAndFixSchema_ExponentialBackoffDelays (t * testing.T ) {
652+ var requestCount atomic.Int32
653+
654+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
655+ requestCount .Add (1 )
656+ w .WriteHeader (http .StatusTooManyRequests )
657+ }))
658+ defer server .Close ()
659+
660+ _ , err := fetchAndFixSchema (server .URL )
661+
662+ require .Error (t , err )
663+ assert .Equal (t , int32 (maxSchemaFetchRetries ), requestCount .Load (),
664+ "should make exactly maxSchemaFetchRetries requests before giving up" )
665+ assert .Contains (t , err .Error (), "failed to fetch schema: HTTP 429" )
666+ }
0 commit comments