feat(zarinhub): automate access token refresh

This commit is contained in:
2026-09-21 16:27:30 +03:30
parent 7f2f26e596
commit c54332cd61
9 changed files with 621 additions and 36 deletions

View File

@@ -97,7 +97,7 @@ func TestClientConvertSuccess(t *testing.T) {
Timeout: time.Second,
},
server.URL,
token,
&fakeTokenProvider{tokens: []string{token}},
)
result, err := client.Convert(
@@ -171,7 +171,7 @@ func TestClientReturnsRejectedForApplicationFailure(
Timeout: time.Second,
},
server.URL,
"test-token",
&fakeTokenProvider{tokens: []string{"test-token"}},
)
result, err := client.Convert(
@@ -263,7 +263,7 @@ func TestClientMapsHTTPStatusErrors(t *testing.T) {
Timeout: time.Second,
},
server.URL,
"test-token",
&fakeTokenProvider{tokens: []string{"test-token"}},
)
result, err := client.Convert(
@@ -321,7 +321,7 @@ func TestClientReturnsInvalidResponseForMalformedJSON(
Timeout: time.Second,
},
server.URL,
"test-token",
&fakeTokenProvider{tokens: []string{"test-token"}},
)
result, err := client.Convert(
@@ -366,7 +366,7 @@ func TestClientReturnsTimeout(t *testing.T) {
Timeout: 50 * time.Millisecond,
},
server.URL,
"test-token",
&fakeTokenProvider{tokens: []string{"test-token"}},
)
_, err := client.Convert(
@@ -402,7 +402,7 @@ func TestClientRejectsOversizedResponse(t *testing.T) {
client := NewClient(
&http.Client{Timeout: time.Second},
server.URL,
"test-token",
&fakeTokenProvider{tokens: []string{"test-token"}},
)
_, err := client.Convert(
@@ -414,3 +414,67 @@ func TestClientRejectsOversizedResponse(t *testing.T) {
t.Fatalf("error = %v, want ErrInvalidResponse", err)
}
}
func TestClientRefreshesAndRetriesUnauthorizedRequest(t *testing.T) {
const expectedIBAN = "IR820540102680020817909002"
requestCount := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestCount++
authorization := r.Header.Get("Authorization")
if requestCount == 1 {
if authorization != "Bearer expired-token" {
t.Errorf("first authorization = %q", authorization)
}
w.WriteHeader(http.StatusUnauthorized)
return
}
if authorization != "Bearer fresh-token" {
t.Errorf("second authorization = %q", authorization)
}
fmt.Fprintf(w, `{
"data":{"iban":%q},
"meta":{"isSuccess":true,"errors":[]}
}`, expectedIBAN)
}))
defer server.Close()
tokens := &fakeTokenProvider{tokens: []string{"expired-token", "fresh-token"}}
client := NewClient(server.Client(), server.URL, tokens)
result, err := client.Convert(context.Background(), "6037997512345678")
if err != nil {
t.Fatalf("Convert() error = %v", err)
}
if result.IBAN != expectedIBAN {
t.Errorf("IBAN = %q, want %q", result.IBAN, expectedIBAN)
}
if requestCount != 2 {
t.Errorf("request count = %d, want 2", requestCount)
}
if len(tokens.invalidated) != 1 || tokens.invalidated[0] != "expired-token" {
t.Errorf("invalidated tokens = %v", tokens.invalidated)
}
}
type fakeTokenProvider struct {
tokens []string
calls int
invalidated []string
}
func (p *fakeTokenProvider) AccessToken(context.Context) (string, error) {
index := p.calls
if index >= len(p.tokens) {
index = len(p.tokens) - 1
}
p.calls++
return p.tokens[index], nil
}
func (p *fakeTokenProvider) Invalidate(token string) {
p.invalidated = append(p.invalidated, token)
}