feat(client): implement exponential backoff retry for network errors

This commit is contained in:
2026-09-21 21:44:12 +03:30
parent 099ec2150e
commit c6e2ce7fd5
6 changed files with 335 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
@@ -15,6 +16,7 @@ type config struct {
ZarinHubUsername string
ZarinHubPassword string
ZarinHubTimeout time.Duration
ZarinHubRetries int
DatabasePath string
}
@@ -44,6 +46,18 @@ func readConfig(getenv func(string) string) (config, error) {
)
}
retriesText := valueOrDefault(
getenv("ZARINHUB_RETRIES"),
"2",
)
retries, err := strconv.Atoi(retriesText)
if err != nil || retries < 0 {
return config{}, fmt.Errorf(
"ZARINHUB_RETRIES must be a non-negative integer",
)
}
return config{
ServerAddr: valueOrDefault(
getenv("SERVER_ADDR"),
@@ -64,6 +78,7 @@ func readConfig(getenv func(string) string) (config, error) {
ZarinHubUsername: username,
ZarinHubPassword: password,
ZarinHubTimeout: timeout,
ZarinHubRetries: retries,
DatabasePath: valueOrDefault(
getenv("DATABASE_PATH"),
"./card-to-iban.db",

View File

@@ -31,6 +31,9 @@ func TestReadConfigUsesDefaults(t *testing.T) {
if config.ZarinHubTimeout != 5*time.Second {
t.Errorf("ZarinHubTimeout = %v", config.ZarinHubTimeout)
}
if config.ZarinHubRetries != 2 {
t.Errorf("ZarinHubRetries = %d, want 2", config.ZarinHubRetries)
}
if config.DatabasePath != "./card-to-iban.db" {
t.Errorf("DatabasePath = %q", config.DatabasePath)
}
@@ -70,3 +73,22 @@ func TestReadConfigRejectsInvalidTimeout(t *testing.T) {
t.Fatalf("readConfig() error = %v, want timeout error", err)
}
}
func TestReadConfigRejectsInvalidRetries(t *testing.T) {
testCases := []string{"-1", "invalid", "2.5"}
for _, tc := range testCases {
values := map[string]string{
"ZARINHUB_API_USERNAME": "test-username",
"ZARINHUB_API_PASSWORD": "test-password",
"ZARINHUB_RETRIES": tc,
}
_, err := readConfig(func(key string) string {
return values[key]
})
if err == nil || !strings.Contains(err.Error(), "ZARINHUB_RETRIES") {
t.Errorf("retries=%q: want error mentioning ZARINHUB_RETRIES, got %v", tc, err)
}
}
}

View File

@@ -79,6 +79,7 @@ func run(logger *slog.Logger) error {
httpClient,
config.ZarinHubEndpoint,
tokenManager,
config.ZarinHubRetries,
)
cardService := service.NewCardService(zarinHubClient)
auditRepository := repository.NewAuditRepository(db)