feat(zarinhub): automate access token refresh
This commit is contained in:
@@ -8,12 +8,14 @@ import (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ServerAddr string
|
||||
FrontendOrigin string
|
||||
ZarinHubEndpoint string
|
||||
ZarinHubToken string
|
||||
ZarinHubTimeout time.Duration
|
||||
DatabasePath string
|
||||
ServerAddr string
|
||||
FrontendOrigin string
|
||||
ZarinHubEndpoint string
|
||||
ZarinHubAuthEndpoint string
|
||||
ZarinHubUsername string
|
||||
ZarinHubPassword string
|
||||
ZarinHubTimeout time.Duration
|
||||
DatabasePath string
|
||||
}
|
||||
|
||||
func loadConfig() (config, error) {
|
||||
@@ -21,9 +23,14 @@ func loadConfig() (config, error) {
|
||||
}
|
||||
|
||||
func readConfig(getenv func(string) string) (config, error) {
|
||||
token := strings.TrimSpace(getenv("ZARINHUB_ACCESS_TOKEN"))
|
||||
if token == "" {
|
||||
return config{}, fmt.Errorf("ZARINHUB_ACCESS_TOKEN is required")
|
||||
username := strings.TrimSpace(getenv("ZARINHUB_API_USERNAME"))
|
||||
if username == "" {
|
||||
return config{}, fmt.Errorf("ZARINHUB_API_USERNAME is required")
|
||||
}
|
||||
|
||||
password := strings.TrimSpace(getenv("ZARINHUB_API_PASSWORD"))
|
||||
if password == "" {
|
||||
return config{}, fmt.Errorf("ZARINHUB_API_PASSWORD is required")
|
||||
}
|
||||
|
||||
timeoutText := valueOrDefault(
|
||||
@@ -50,8 +57,13 @@ func readConfig(getenv func(string) string) (config, error) {
|
||||
getenv("ZARINHUB_ENDPOINT"),
|
||||
"https://zarin-hub.com/api/v5/KYC/CardToIban",
|
||||
),
|
||||
ZarinHubToken: token,
|
||||
ZarinHubTimeout: timeout,
|
||||
ZarinHubAuthEndpoint: valueOrDefault(
|
||||
getenv("ZARINHUB_AUTH_ENDPOINT"),
|
||||
"https://zarin-hub.com/api/v5/Authentication/GetToken",
|
||||
),
|
||||
ZarinHubUsername: username,
|
||||
ZarinHubPassword: password,
|
||||
ZarinHubTimeout: timeout,
|
||||
DatabasePath: valueOrDefault(
|
||||
getenv("DATABASE_PATH"),
|
||||
"./card-to-iban.db",
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
|
||||
func TestReadConfigUsesDefaults(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"ZARINHUB_ACCESS_TOKEN": "test-token",
|
||||
"ZARINHUB_API_USERNAME": "test-username",
|
||||
"ZARINHUB_API_PASSWORD": "test-password",
|
||||
}
|
||||
|
||||
config, err := readConfig(func(key string) string {
|
||||
@@ -24,6 +25,9 @@ func TestReadConfigUsesDefaults(t *testing.T) {
|
||||
if config.FrontendOrigin != "http://127.0.0.1:3000" {
|
||||
t.Errorf("FrontendOrigin = %q", config.FrontendOrigin)
|
||||
}
|
||||
if config.ZarinHubAuthEndpoint != "https://zarin-hub.com/api/v5/Authentication/GetToken" {
|
||||
t.Errorf("ZarinHubAuthEndpoint = %q", config.ZarinHubAuthEndpoint)
|
||||
}
|
||||
if config.ZarinHubTimeout != 5*time.Second {
|
||||
t.Errorf("ZarinHubTimeout = %v", config.ZarinHubTimeout)
|
||||
}
|
||||
@@ -32,16 +36,30 @@ func TestReadConfigUsesDefaults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigRejectsMissingAccessToken(t *testing.T) {
|
||||
func TestReadConfigRejectsMissingUsername(t *testing.T) {
|
||||
_, err := readConfig(func(key string) string { return "" })
|
||||
if err == nil || !strings.Contains(err.Error(), "ZARINHUB_ACCESS_TOKEN") {
|
||||
t.Fatalf("readConfig() error = %v, want missing token error", err)
|
||||
if err == nil || !strings.Contains(err.Error(), "ZARINHUB_API_USERNAME") {
|
||||
t.Fatalf("readConfig() error = %v, want missing username error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigRejectsMissingPassword(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"ZARINHUB_API_USERNAME": "test-username",
|
||||
}
|
||||
|
||||
_, err := readConfig(func(key string) string {
|
||||
return values[key]
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "ZARINHUB_API_PASSWORD") {
|
||||
t.Fatalf("readConfig() error = %v, want missing password error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigRejectsInvalidTimeout(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"ZARINHUB_ACCESS_TOKEN": "test-token",
|
||||
"ZARINHUB_API_USERNAME": "test-username",
|
||||
"ZARINHUB_API_PASSWORD": "test-password",
|
||||
"ZARINHUB_TIMEOUT": "later",
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,24 @@ func run(logger *slog.Logger) error {
|
||||
return fmt.Errorf("load configuration: %w", err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Timeout: config.ZarinHubTimeout}
|
||||
tokenManager := zarinhub.NewTokenManager(
|
||||
httpClient,
|
||||
config.ZarinHubAuthEndpoint,
|
||||
config.ZarinHubUsername,
|
||||
config.ZarinHubPassword,
|
||||
)
|
||||
|
||||
authContext, cancelAuth := context.WithTimeout(
|
||||
context.Background(),
|
||||
config.ZarinHubTimeout,
|
||||
)
|
||||
_, err = tokenManager.AccessToken(authContext)
|
||||
cancelAuth()
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticate with zarinhub: %w", err)
|
||||
}
|
||||
|
||||
db, err := storage.OpenSQLite(config.DatabasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,9 +76,9 @@ func run(logger *slog.Logger) error {
|
||||
}
|
||||
|
||||
zarinHubClient := zarinhub.NewClient(
|
||||
&http.Client{Timeout: config.ZarinHubTimeout},
|
||||
httpClient,
|
||||
config.ZarinHubEndpoint,
|
||||
config.ZarinHubToken,
|
||||
tokenManager,
|
||||
)
|
||||
cardService := service.NewCardService(zarinHubClient)
|
||||
auditRepository := repository.NewAuditRepository(db)
|
||||
|
||||
Reference in New Issue
Block a user