返回 DeepSeek-Reasonix
balance_extra_test.go
根目录 / internal / billing / balance_extra_test.go
1 package billing
2
3 import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9 )
10
11 // --- symbol ---
12
13 func TestSymbolCNY(t *testing.T) {
14 if got := symbol("CNY"); got != "¥" {
15 t.Errorf("symbol(CNY) = %q", got)
16 }
17 }
18
19 func TestSymbolRMB(t *testing.T) {
20 if got := symbol("RMB"); got != "¥" {
21 t.Errorf("symbol(RMB) = %q", got)
22 }
23 }
24
25 func TestSymbolUSD(t *testing.T) {
26 if got := symbol("USD"); got != "$" {
27 t.Errorf("symbol(USD) = %q", got)
28 }
29 }
30
31 func TestSymbolUnknown(t *testing.T) {
32 if got := symbol("EUR"); got != "EUR " {
33 t.Errorf("symbol(EUR) = %q, want \"EUR \"", got)
34 }
35 }
36
37 func TestSymbolEmpty(t *testing.T) {
38 if got := symbol(""); got != "" {
39 t.Errorf("symbol(\"\") = %q, want empty", got)
40 }
41 }
42
43 func TestSymbolLowercase(t *testing.T) {
44 // symbol should be case-insensitive.
45 if got := symbol("usd"); got != "$" {
46 t.Errorf("symbol(usd) = %q", got)
47 }
48 }
49
50 // --- Display ---
51
52 func TestDisplayNil(t *testing.T) {
53 var b *Balance
54 if got := b.Display(); got != "" {
55 t.Errorf("nil Display = %q", got)
56 }
57 }
58
59 func TestDisplayEmptyInfos(t *testing.T) {
60 b := &Balance{Available: true}
61 if got := b.Display(); got != "" {
62 t.Errorf("empty infos Display = %q", got)
63 }
64 }
65
66 func TestDisplayPrefersCNY(t *testing.T) {
67 b := &Balance{Infos: []Info{
68 {Currency: "USD", TotalBalance: "10.00"},
69 {Currency: "CNY", TotalBalance: "50.00"},
70 }}
71 if got := b.Display(); got != "¥50.00" {
72 t.Errorf("Display = %q, want ¥50.00", got)
73 }
74 }
75
76 func TestDisplayFallsBackToFirst(t *testing.T) {
77 b := &Balance{Infos: []Info{
78 {Currency: "EUR", TotalBalance: "25.00"},
79 }}
80 if got := b.Display(); got != "EUR 25.00" {
81 t.Errorf("Display = %q, want \"EUR 25.00\"", got)
82 }
83 }
84
85 // --- Fetch edge cases ---
86
87 func TestFetchContextCancelled(t *testing.T) {
88 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
89 w.WriteHeader(http.StatusOK)
90 }))
91 defer srv.Close()
92 ctx, cancel := context.WithCancel(context.Background())
93 cancel() // cancel immediately
94 _, err := Fetch(ctx, srv.URL, "key")
95 if err == nil {
96 t.Fatal("expected error for cancelled context")
97 }
98 }
99
100 func TestFetchMalformedJSON(t *testing.T) {
101 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
102 w.Header().Set("Content-Type", "application/json")
103 _, _ = w.Write([]byte(`{not valid json`))
104 }))
105 defer srv.Close()
106 _, err := Fetch(context.Background(), srv.URL, "key")
107 if err == nil {
108 t.Fatal("expected error for malformed JSON")
109 }
110 if !strings.Contains(err.Error(), "decode") {
111 t.Errorf("error should mention decode: %v", err)
112 }
113 }
114
115 func TestFetchNoAPIKey(t *testing.T) {
116 var gotAuth string
117 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 gotAuth = r.Header.Get("Authorization")
119 w.Header().Set("Content-Type", "application/json")
120 _, _ = w.Write([]byte(`{"is_available":true,"balance_infos":[]}`))
121 }))
122 defer srv.Close()
123 _, err := Fetch(context.Background(), srv.URL, "")
124 if err != nil {
125 t.Fatalf("Fetch: %v", err)
126 }
127 if gotAuth != "" {
128 t.Errorf("Authorization should be empty when no key, got %q", gotAuth)
129 }
130 }
131
132 func TestFetchWhitespaceURL(t *testing.T) {
133 b, err := Fetch(context.Background(), " ", "key")
134 if err != nil || b != nil {
135 t.Fatalf("whitespace URL should return (nil,nil), got (%v, %v)", b, err)
136 }
137 }
138
139 func TestFetchServerUnavailable(t *testing.T) {
140 // Use a URL that won't connect.
141 _, err := Fetch(context.Background(), "http://127.0.0.1:1", "key")
142 if err == nil {
143 t.Fatal("expected error for unavailable server")
144 }
145 }
146
146 lines GO