claude_usage_service_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package repository
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "github.com/stretchr/testify/require"
  9. "github.com/stretchr/testify/suite"
  10. )
  11. type ClaudeUsageServiceSuite struct {
  12. suite.Suite
  13. srv *httptest.Server
  14. fetcher *claudeUsageService
  15. }
  16. func (s *ClaudeUsageServiceSuite) TearDownTest() {
  17. if s.srv != nil {
  18. s.srv.Close()
  19. s.srv = nil
  20. }
  21. }
  22. // usageRequestCapture holds captured request data for assertions in the main goroutine.
  23. type usageRequestCapture struct {
  24. authorization string
  25. anthropicBeta string
  26. }
  27. func (s *ClaudeUsageServiceSuite) TestFetchUsage_Success() {
  28. var captured usageRequestCapture
  29. s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  30. captured.authorization = r.Header.Get("Authorization")
  31. captured.anthropicBeta = r.Header.Get("anthropic-beta")
  32. w.Header().Set("Content-Type", "application/json")
  33. _, _ = io.WriteString(w, `{
  34. "five_hour": {"utilization": 12.5, "resets_at": "2025-01-01T00:00:00Z"},
  35. "seven_day": {"utilization": 34.0, "resets_at": "2025-01-08T00:00:00Z"},
  36. "seven_day_sonnet": {"utilization": 56.0, "resets_at": "2025-01-08T00:00:00Z"}
  37. }`)
  38. }))
  39. s.fetcher = &claudeUsageService{
  40. usageURL: s.srv.URL,
  41. allowPrivateHosts: true,
  42. }
  43. resp, err := s.fetcher.FetchUsage(context.Background(), "at", "")
  44. require.NoError(s.T(), err, "FetchUsage")
  45. require.Equal(s.T(), 12.5, resp.FiveHour.Utilization, "FiveHour utilization mismatch")
  46. require.Equal(s.T(), 34.0, resp.SevenDay.Utilization, "SevenDay utilization mismatch")
  47. require.Equal(s.T(), 56.0, resp.SevenDaySonnet.Utilization, "SevenDaySonnet utilization mismatch")
  48. // Assertions on captured request data
  49. require.Equal(s.T(), "Bearer at", captured.authorization, "Authorization header mismatch")
  50. require.Equal(s.T(), "oauth-2025-04-20", captured.anthropicBeta, "anthropic-beta header mismatch")
  51. }
  52. func (s *ClaudeUsageServiceSuite) TestFetchUsage_NonOK() {
  53. s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  54. w.WriteHeader(http.StatusUnauthorized)
  55. _, _ = io.WriteString(w, "nope")
  56. }))
  57. s.fetcher = &claudeUsageService{
  58. usageURL: s.srv.URL,
  59. allowPrivateHosts: true,
  60. }
  61. _, err := s.fetcher.FetchUsage(context.Background(), "at", "")
  62. require.Error(s.T(), err)
  63. require.ErrorContains(s.T(), err, "status 401")
  64. require.ErrorContains(s.T(), err, "nope")
  65. }
  66. func (s *ClaudeUsageServiceSuite) TestFetchUsage_BadJSON() {
  67. s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  68. w.Header().Set("Content-Type", "application/json")
  69. _, _ = io.WriteString(w, "not-json")
  70. }))
  71. s.fetcher = &claudeUsageService{
  72. usageURL: s.srv.URL,
  73. allowPrivateHosts: true,
  74. }
  75. _, err := s.fetcher.FetchUsage(context.Background(), "at", "")
  76. require.Error(s.T(), err)
  77. require.ErrorContains(s.T(), err, "decode response failed")
  78. }
  79. func (s *ClaudeUsageServiceSuite) TestFetchUsage_ContextCancel() {
  80. s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  81. // Never respond - simulate slow server
  82. <-r.Context().Done()
  83. }))
  84. s.fetcher = &claudeUsageService{
  85. usageURL: s.srv.URL,
  86. allowPrivateHosts: true,
  87. }
  88. ctx, cancel := context.WithCancel(context.Background())
  89. cancel() // Cancel immediately
  90. _, err := s.fetcher.FetchUsage(ctx, "at", "")
  91. require.Error(s.T(), err, "expected error for cancelled context")
  92. }
  93. func (s *ClaudeUsageServiceSuite) TestFetchUsage_InvalidProxyReturnsError() {
  94. s.fetcher = &claudeUsageService{
  95. usageURL: "http://example.com",
  96. allowPrivateHosts: true,
  97. }
  98. _, err := s.fetcher.FetchUsage(context.Background(), "at", "://bad-proxy-url")
  99. require.Error(s.T(), err)
  100. require.ErrorContains(s.T(), err, "create http client failed")
  101. }
  102. func TestClaudeUsageServiceSuite(t *testing.T) {
  103. suite.Run(t, new(ClaudeUsageServiceSuite))
  104. }