fix: recover transient stock history failures within bounded deadlines
This commit is contained in:
@@ -90,6 +90,9 @@ func (s *MarketService) History(ctx context.Context, u model.User, symbol, excha
|
||||
return model.MarketHistory{}, e
|
||||
}
|
||||
result, e := s.adapter.History(ctx, c, symbol, exchange, start, end)
|
||||
if e != nil && ctx.Err() == nil {
|
||||
result, e = s.adapter.History(ctx, c, symbol, exchange, start, end)
|
||||
}
|
||||
if e != nil {
|
||||
return result, apperror.ErrUnavailable
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
|
||||
"git.sundynix.cn/Blizzard/Veyra/internal/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type historyProvider struct{ ProviderRepository }
|
||||
|
||||
func (historyProvider) Get(context.Context, string) (model.ProviderConfig, error) {
|
||||
return model.ProviderConfig{Enabled: true, MarketAdapter: "sina_public"}, nil
|
||||
}
|
||||
|
||||
type historyRepo struct{ MarketRepository }
|
||||
type retryHistory struct {
|
||||
MarketAdapter
|
||||
calls int
|
||||
cancel context.CancelFunc
|
||||
alwaysFail bool
|
||||
}
|
||||
|
||||
func (a *retryHistory) History(ctx context.Context, _ model.ProviderConfig, symbol, exchange, start, end string) (model.MarketHistory, error) {
|
||||
a.calls++
|
||||
if a.cancel != nil {
|
||||
a.cancel()
|
||||
}
|
||||
if a.calls == 1 || a.alwaysFail {
|
||||
return model.MarketHistory{}, errors.New("upstream failure")
|
||||
}
|
||||
return model.MarketHistory{Symbol: symbol, Exchange: exchange, Bars: []model.HistoricalBar{{Date: start, Close: "9.41"}}}, nil
|
||||
}
|
||||
func TestHistoryRetriesOnceAndHonorsCancellation(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cancel, fail bool
|
||||
calls int
|
||||
}{{"recovers", false, false, 2}, {"bounded failure", false, true, 2}, {"cancelled", true, false, 1}} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
adapter := &retryHistory{alwaysFail: tc.fail}
|
||||
if tc.cancel {
|
||||
adapter.cancel = cancel
|
||||
}
|
||||
s := NewMarketService(historyRepo{}, historyProvider{}, adapter, true)
|
||||
result, err := s.History(ctx, model.User{Role: "ADMIN", Status: "ACTIVE"}, "002215", "SZSE", "2026-07-01", "2026-07-02")
|
||||
if adapter.calls != tc.calls {
|
||||
t.Fatalf("calls: %d", adapter.calls)
|
||||
}
|
||||
if tc.fail || tc.cancel {
|
||||
if !errors.Is(err, apperror.ErrUnavailable) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if err != nil || result.Symbol != "002215" || len(result.Bars) != 1 {
|
||||
t.Fatal("retry did not preserve real result", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user