返回 DeepSeek-Reasonix
balance_test.go
根目录 / internal / billing / balance_test.go
1 package billing
2
3 import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8 )
9
10 // A DeepSeek-shaped response parses, exposes Available, and Display prefers CNY
11 // with the right symbol; the request carries the bearer key.
12 func TestFetchDeepSeekShape(t *testing.T) {
13 const body = `{
14 "is_available": true,
15 "balance_infos": [
16 {"currency": "USD", "total_balance": "15.30", "granted_balance": "0.00", "topped_up_balance": "15.30"},
17 {"currency": "CNY", "total_balance": "110.00", "granted_balance": "10.00", "topped_up_balance": "100.00"}
18 ]
19 }`
20 var gotAuth string
21 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 gotAuth = r.Header.Get("Authorization")
23 w.Header().Set("Content-Type", "application/json")
24 _, _ = w.Write([]byte(body))
25 }))
26 defer srv.Close()
27
28 b, err := Fetch(context.Background(), srv.URL, "secret-key")
29 if err != nil {
30 t.Fatalf("Fetch: %v", err)
31 }
32 if b == nil || !b.Available {
33 t.Fatalf("want available balance, got %+v", b)
34 }
35 if gotAuth != "Bearer secret-key" {
36 t.Errorf("Authorization = %q, want %q", gotAuth, "Bearer secret-key")
37 }
38 if len(b.Infos) != 2 {
39 t.Fatalf("want 2 infos, got %d", len(b.Infos))
40 }
41 // Display prefers CNY → "¥110.00", not the first (USD) entry.
42 if got := b.Display(); got != "¥110.00" {
43 t.Errorf("Display = %q, want %q", got, "¥110.00")
44 }
45 if got := b.DisplayForCurrency("USD"); got != "$15.30" {
46 t.Errorf("DisplayForCurrency(USD) = %q, want %q", got, "$15.30")
47 }
48 if got := b.DisplayForCurrency("¥"); got != "¥110.00" {
49 t.Errorf("DisplayForCurrency(¥) = %q, want %q", got, "¥110.00")
50 }
51 }
52
53 // An empty url is "not configured", not an error: (nil, nil), and Display on a nil
54 // balance is "".
55 func TestFetchEmptyURL(t *testing.T) {
56 b, err := Fetch(context.Background(), "", "key")
57 if err != nil || b != nil {
58 t.Fatalf("Fetch(\"\") = (%v, %v), want (nil, nil)", b, err)
59 }
60 if got := b.Display(); got != "" {
61 t.Errorf("nil Display = %q, want empty", got)
62 }
63 }
64
65 // A non-200 surfaces an error rather than a bogus zero balance.
66 func TestFetchHTTPError(t *testing.T) {
67 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
68 w.WriteHeader(http.StatusUnauthorized)
69 _, _ = w.Write([]byte(`{"error":"invalid key"}`))
70 }))
71 defer srv.Close()
72 if _, err := Fetch(context.Background(), srv.URL, "bad"); err == nil {
73 t.Fatal("want error on 401, got nil")
74 }
75 }
76
77 // Display falls back to the first currency when no CNY entry is present, and maps
78 // USD to "$".
79 func TestDisplayUSDOnly(t *testing.T) {
80 b := &Balance{Available: true, Infos: []Info{{Currency: "USD", TotalBalance: "9.99"}}}
81 if got := b.Display(); got != "$9.99" {
82 t.Errorf("Display = %q, want %q", got, "$9.99")
83 }
84 if got := b.DisplayForCurrency("CNY"); got != "USD $9.99" {
85 t.Errorf("DisplayForCurrency(CNY) = %q, want explicit real fallback currency %q", got, "USD $9.99")
86 }
87 }
88
88 lines GO