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) } }) } }