cn_provider_balance_service_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package service
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "strings"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
  9. "github.com/stretchr/testify/require"
  10. )
  11. type cnBalanceResponseUpstream struct {
  12. statusCode int
  13. body string
  14. }
  15. func (u *cnBalanceResponseUpstream) Do(
  16. _ *http.Request,
  17. _ string,
  18. _ int64,
  19. _ int,
  20. ) (*http.Response, error) {
  21. return &http.Response{
  22. StatusCode: u.statusCode,
  23. Body: io.NopCloser(strings.NewReader(u.body)),
  24. Header: make(http.Header),
  25. }, nil
  26. }
  27. func (u *cnBalanceResponseUpstream) DoWithTLS(
  28. req *http.Request,
  29. proxyURL string,
  30. accountID int64,
  31. accountConcurrency int,
  32. _ *tlsfingerprint.Profile,
  33. ) (*http.Response, error) {
  34. return u.Do(req, proxyURL, accountID, accountConcurrency)
  35. }
  36. type cnBalanceProbeRepo struct {
  37. AccountRepository
  38. account *Account
  39. extraWrites []map[string]any
  40. }
  41. func (r *cnBalanceProbeRepo) GetByID(_ context.Context, _ int64) (*Account, error) {
  42. return r.account, nil
  43. }
  44. func (r *cnBalanceProbeRepo) UpdateExtra(_ context.Context, _ int64, updates map[string]any) error {
  45. r.extraWrites = append(r.extraWrites, updates)
  46. return nil
  47. }
  48. func newDeepSeekBalanceProbeAccount() *Account {
  49. return &Account{
  50. ID: 42,
  51. Platform: PlatformDeepseek,
  52. Type: AccountTypeAPIKey,
  53. Status: StatusActive,
  54. Credentials: map[string]any{
  55. "account_mode": AccountModePayG,
  56. "api_key": "sk-test",
  57. "base_url": "https://relay.example.com",
  58. },
  59. }
  60. }
  61. func TestCNProviderBalanceService_DeepSeekInvalidBalancePayloadDoesNotBecomeZero(t *testing.T) {
  62. tests := []struct {
  63. name string
  64. body string
  65. wantError string
  66. }{
  67. {
  68. name: "missing balance infos",
  69. body: `{"data":{"models":["deepseek-v4-flash"]}}`,
  70. wantError: "missing balance_infos",
  71. },
  72. {
  73. name: "empty balance infos",
  74. body: `{"is_available":true,"balance_infos":[]}`,
  75. wantError: "no valid balance entries",
  76. },
  77. {
  78. name: "invalid balance value",
  79. body: `{"is_available":true,"balance_infos":[{"currency":"CNY","total_balance":"not-a-number"}]}`,
  80. wantError: "no valid balance entries",
  81. },
  82. }
  83. for _, tt := range tests {
  84. t.Run(tt.name, func(t *testing.T) {
  85. repo := &cnBalanceProbeRepo{account: newDeepSeekBalanceProbeAccount()}
  86. upstream := &cnBalanceResponseUpstream{statusCode: http.StatusOK, body: tt.body}
  87. svc := NewCNProviderBalanceService(repo, nil, upstream, nil)
  88. result, err := svc.QueryBalance(context.Background(), repo.account.ID)
  89. require.NoError(t, err)
  90. require.NotNil(t, result)
  91. require.False(t, result.Success)
  92. require.Contains(t, result.Error, tt.wantError)
  93. require.Empty(t, result.Balances)
  94. require.Empty(t, repo.extraWrites, "invalid relay balance payload must not persist a synthetic zero balance")
  95. })
  96. }
  97. }
  98. func TestCNProviderBalanceService_DeepSeekValidZeroBalanceRemainsSuccessful(t *testing.T) {
  99. repo := &cnBalanceProbeRepo{account: newDeepSeekBalanceProbeAccount()}
  100. upstream := &cnBalanceResponseUpstream{
  101. statusCode: http.StatusOK,
  102. body: `{"is_available":false,"balance_infos":[{"currency":"CNY","total_balance":"0"}]}`,
  103. }
  104. svc := NewCNProviderBalanceService(repo, nil, upstream, nil)
  105. result, err := svc.QueryBalance(context.Background(), repo.account.ID)
  106. require.NoError(t, err)
  107. require.NotNil(t, result)
  108. require.True(t, result.Success)
  109. require.False(t, result.Available)
  110. require.Equal(t, "CNY", result.Currency)
  111. require.Zero(t, result.Balance)
  112. require.Len(t, result.Balances, 1)
  113. require.Len(t, repo.extraWrites, 1, "a valid upstream zero balance must still be persisted")
  114. }