package api import ( "context" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" ) func TestFetchSubscriptionDetail_Success(t *testing.T) { body, err := os.ReadFile(filepath.Join("testdata", "sample.json")) if err != nil { t.Fatalf("read sample: %v", err) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.Header.Get("Authorization"); got != "Bearer test-key" { t.Errorf("Authorization = %q", got) } if r.URL.Path != "/api/v1/management/subscription/detail" { t.Errorf("path = %q", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write(body) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTPClient: srv.Client()} resp, raw, err := c.FetchSubscriptionDetail(context.Background(), "test-key") if err != nil { t.Fatalf("unexpected error: %v", err) } if !resp.Success { t.Error("resp.Success = false") } if resp.Data.Plan.Tier != "ultra" { t.Errorf("plan.tier = %q", resp.Data.Plan.Tier) } if resp.Data.Quota7Day.UsedValueUSD != 13.66 { t.Errorf("quota_7_day.used_value_usd = %v", resp.Data.Quota7Day.UsedValueUSD) } if resp.Data.QuotaMonthly.MaxValueUSD != 1134.33 { t.Errorf("quota_monthly.max_value_usd = %v", resp.Data.QuotaMonthly.MaxValueUSD) } if len(raw) != len(body) { t.Errorf("raw body length mismatch: got %d want %d", len(raw), len(body)) } } func TestFetchSubscriptionDetail_StatusMapping(t *testing.T) { cases := []struct { name string status int target error }{ {"401", http.StatusUnauthorized, ErrUnauthorized}, {"403", http.StatusForbidden, ErrUnauthorized}, {"422", http.StatusUnprocessableEntity, ErrRateLimited}, {"429", http.StatusTooManyRequests, ErrRateLimited}, {"500", http.StatusInternalServerError, ErrServer}, {"502", http.StatusBadGateway, ErrServer}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(tc.status) _, _ = w.Write([]byte(`{"success":false,"error":"x"}`)) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTPClient: srv.Client()} _, _, err := c.FetchSubscriptionDetail(context.Background(), "k") if !errors.Is(err, tc.target) { t.Fatalf("want %v, got %v", tc.target, err) } }) } } func TestFetchSubscriptionDetail_MalformedJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"success": not-json`)) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTPClient: srv.Client()} _, _, err := c.FetchSubscriptionDetail(context.Background(), "k") if !errors.Is(err, ErrBadResponse) { t.Fatalf("want ErrBadResponse, got %v", err) } } func TestFetchSubscriptionDetail_SuccessFalse(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"success":false,"error":"explicit failure"}`)) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTPClient: srv.Client()} _, _, err := c.FetchSubscriptionDetail(context.Background(), "k") if !errors.Is(err, ErrBadResponse) { t.Fatalf("want ErrBadResponse, got %v", err) } } func TestFetchSubscriptionDetail_Timeout(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(100 * time.Millisecond) w.WriteHeader(http.StatusOK) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTPClient: &http.Client{Timeout: 5 * time.Millisecond}} _, _, err := c.FetchSubscriptionDetail(context.Background(), "k") if !errors.Is(err, ErrTimeout) { t.Fatalf("want ErrTimeout, got %v", err) } }