@@ -5,12 +5,14 @@ import (
55 "crypto/tls"
66 "fmt"
77 "net/http"
8+ "net/http/httptest"
89 "os"
910 "reflect"
1011 "testing"
1112 "testing/synctest"
1213 "time"
1314
15+ "github.com/google/uuid"
1416 "github.com/hashicorp/terraform-plugin-framework/diag"
1517 sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients"
1618 "github.com/stackitcloud/stackit-sdk-go/core/config"
@@ -61,7 +63,7 @@ func TestConfigureClient(t *testing.T) {
6163 config .WithCustomAuth (& RetryTransport {
6264 Base : roundTripper ,
6365 MaxRetries : 3 ,
64- BaseBackoff : 1 * time .Second ,
66+ BaseBackoff : 10 * time .Second ,
6567 MaxJitter : 500 * time .Millisecond ,
6668 }),
6769 )
@@ -89,7 +91,7 @@ func TestConfigureClient(t *testing.T) {
8991 config .WithCustomAuth (& RetryTransport {
9092 Base : roundTripper ,
9193 MaxRetries : 3 ,
92- BaseBackoff : 1 * time .Second ,
94+ BaseBackoff : 10 * time .Second ,
9395 MaxJitter : 500 * time .Millisecond ,
9496 }),
9597 )
@@ -118,6 +120,81 @@ func TestConfigureClient(t *testing.T) {
118120 }
119121}
120122
123+ func TestClientRetry (t * testing.T ) {
124+ ctx := context .Background ()
125+ diags := diag.Diagnostics {}
126+
127+ testProjectId := uuid .New ().String ()
128+ const testRegion = "eu01"
129+ const testBucketName = "karl-otto"
130+
131+ attempts := 0
132+
133+ // Create mock server returning HTTP 429 on first & second call, HTTP 200 on final retry
134+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
135+ attempts ++
136+
137+ if r .URL .Path != fmt .Sprintf ("/v2/project/%s/regions/%s/bucket/%s" , testProjectId , testRegion , testBucketName ) {
138+ t .Fatalf ("invalid endpoint called" )
139+ }
140+
141+ // first request: HTTP 429 *with* Retry-After header
142+ if attempts == 1 {
143+ w .Header ().Set ("Retry-After" , "1" )
144+ w .Header ().Set ("Content-Type" , "application/json" )
145+ w .WriteHeader (http .StatusTooManyRequests )
146+ _ , err := w .Write ([]byte (`{"error": "rate_limit_exceeded"}` ))
147+ if err != nil {
148+ t .Fatalf ("error writing response: %v" , err )
149+ }
150+ return
151+ }
152+
153+ // second request: HTTP 429 *without* Retry-After header (we expect base backoff to be used now)
154+ if attempts == 2 {
155+ w .Header ().Set ("Content-Type" , "application/json" )
156+ w .WriteHeader (http .StatusTooManyRequests )
157+ _ , err := w .Write ([]byte (`{"error": "rate_limit_exceeded"}` ))
158+ if err != nil {
159+ t .Fatalf ("error writing response: %v" , err )
160+ }
161+ return
162+ }
163+
164+ w .Header ().Set ("Content-Type" , "application/json" )
165+ w .WriteHeader (http .StatusOK )
166+ _ , err := w .Write ([]byte (`{
167+ "bucket": {
168+ "name": "bucket-1",
169+ "objectLockEnabled": false,
170+ "region": "eu01",
171+ "urlPathStyle": "https://object.storage.eu01.onstackit.cloud/bucket-1",
172+ "urlVirtualHostedStyle": "https://bucket-1.object.storage.eu01.onstackit.cloud"
173+ },
174+ "project": "` + testProjectId + `"}` ))
175+ if err != nil {
176+ t .Fatalf ("error writing response: %v" , err )
177+ }
178+ }))
179+ defer server .Close ()
180+
181+ client := ConfigureClient (ctx , & core.ProviderData {
182+ ObjectStorageCustomEndpoint : server .URL ,
183+ }, & diags )
184+ if diags .HasError () {
185+ t .Fatalf ("error configuring client: %v" , diags )
186+ }
187+
188+ _ , err := client .DefaultAPI .GetBucket (ctx , testProjectId , testRegion , testBucketName ).Execute ()
189+ if err != nil {
190+ t .Fatalf ("unexpected request error: %v" , err )
191+ }
192+
193+ if attempts != 3 {
194+ t .Fatalf ("expected 3 attempts, got %d" , attempts )
195+ }
196+ }
197+
121198func TestEnableProject (t * testing.T ) {
122199 tests := []struct {
123200 description string
0 commit comments