package service import ( "context" "io" "net/http" "strings" "testing" "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/stretchr/testify/require" ) type cnBalanceResponseUpstream struct { statusCode int body string } func (u *cnBalanceResponseUpstream) Do( _ *http.Request, _ string, _ int64, _ int, ) (*http.Response, error) { return &http.Response{ StatusCode: u.statusCode, Body: io.NopCloser(strings.NewReader(u.body)), Header: make(http.Header), }, nil } func (u *cnBalanceResponseUpstream) DoWithTLS( req *http.Request, proxyURL string, accountID int64, accountConcurrency int, _ *tlsfingerprint.Profile, ) (*http.Response, error) { return u.Do(req, proxyURL, accountID, accountConcurrency) } type cnBalanceProbeRepo struct { AccountRepository account *Account extraWrites []map[string]any } func (r *cnBalanceProbeRepo) GetByID(_ context.Context, _ int64) (*Account, error) { return r.account, nil } func (r *cnBalanceProbeRepo) UpdateExtra(_ context.Context, _ int64, updates map[string]any) error { r.extraWrites = append(r.extraWrites, updates) return nil } func newDeepSeekBalanceProbeAccount() *Account { return &Account{ ID: 42, Platform: PlatformDeepseek, Type: AccountTypeAPIKey, Status: StatusActive, Credentials: map[string]any{ "account_mode": AccountModePayG, "api_key": "sk-test", "base_url": "https://relay.example.com", }, } } func TestCNProviderBalanceService_DeepSeekInvalidBalancePayloadDoesNotBecomeZero(t *testing.T) { tests := []struct { name string body string wantError string }{ { name: "missing balance infos", body: `{"data":{"models":["deepseek-v4-flash"]}}`, wantError: "missing balance_infos", }, { name: "empty balance infos", body: `{"is_available":true,"balance_infos":[]}`, wantError: "no valid balance entries", }, { name: "invalid balance value", body: `{"is_available":true,"balance_infos":[{"currency":"CNY","total_balance":"not-a-number"}]}`, wantError: "no valid balance entries", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := &cnBalanceProbeRepo{account: newDeepSeekBalanceProbeAccount()} upstream := &cnBalanceResponseUpstream{statusCode: http.StatusOK, body: tt.body} svc := NewCNProviderBalanceService(repo, nil, upstream, nil) result, err := svc.QueryBalance(context.Background(), repo.account.ID) require.NoError(t, err) require.NotNil(t, result) require.False(t, result.Success) require.Contains(t, result.Error, tt.wantError) require.Empty(t, result.Balances) require.Empty(t, repo.extraWrites, "invalid relay balance payload must not persist a synthetic zero balance") }) } } func TestCNProviderBalanceService_DeepSeekValidZeroBalanceRemainsSuccessful(t *testing.T) { repo := &cnBalanceProbeRepo{account: newDeepSeekBalanceProbeAccount()} upstream := &cnBalanceResponseUpstream{ statusCode: http.StatusOK, body: `{"is_available":false,"balance_infos":[{"currency":"CNY","total_balance":"0"}]}`, } svc := NewCNProviderBalanceService(repo, nil, upstream, nil) result, err := svc.QueryBalance(context.Background(), repo.account.ID) require.NoError(t, err) require.NotNil(t, result) require.True(t, result.Success) require.False(t, result.Available) require.Equal(t, "CNY", result.Currency) require.Zero(t, result.Balance) require.Len(t, result.Balances, 1) require.Len(t, repo.extraWrites, 1, "a valid upstream zero balance must still be persisted") }