billing_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package xai
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "testing"
  6. "github.com/stretchr/testify/require"
  7. )
  8. func TestBuildBillingURL(t *testing.T) {
  9. t.Parallel()
  10. require.Equal(t, "https://cli-chat-proxy.grok.com/v1/billing?format=credits", BuildBillingURL(true))
  11. require.Equal(t, "https://cli-chat-proxy.grok.com/v1/billing", BuildBillingURL(false))
  12. }
  13. func TestBuildBillingURLWithValidator(t *testing.T) {
  14. t.Parallel()
  15. weeklyURL, err := BuildBillingURLWithValidator(DefaultCLIBaseURL, true, ValidateTrustedBaseURL)
  16. require.NoError(t, err)
  17. require.Equal(t, "https://cli-chat-proxy.grok.com/v1/billing?format=credits", weeklyURL)
  18. monthlyURL, err := BuildBillingURLWithValidator("https://relay.example.test/v1", false, ValidateBaseURL)
  19. require.NoError(t, err)
  20. require.Equal(t, "https://relay.example.test/v1/billing", monthlyURL)
  21. _, err = BuildBillingURLWithValidator("https://relay.example.test/v1", true, ValidateTrustedBaseURL)
  22. require.Error(t, err)
  23. }
  24. func TestApplyCLIBillingHeaders(t *testing.T) {
  25. t.Parallel()
  26. req, err := http.NewRequest(http.MethodGet, BuildBillingURL(true), nil)
  27. require.NoError(t, err)
  28. ApplyCLIBillingHeaders(req, " token ")
  29. require.Equal(t, "Bearer token", req.Header.Get("Authorization"))
  30. require.Equal(t, CLITokenAuthValue, req.Header.Get(CLITokenAuthHeader))
  31. require.Equal(t, CLIClientVersion, req.Header.Get(CLIClientVersionHeader))
  32. require.Equal(t, "grok-pager/"+CLIClientVersion+" grok-shell/"+CLIClientVersion+" (macos; aarch64)", req.UserAgent())
  33. }
  34. func TestBuildBillingSummaryWeeklyAndMonthly(t *testing.T) {
  35. t.Parallel()
  36. weeklyBody := []byte(`{
  37. "config": {
  38. "currentPeriod": {"type":"WEEKLY","start":"2026-07-09T03:25:00Z","end":"2026-07-16T03:25:00Z"},
  39. "creditUsagePercent": 2.0,
  40. "productUsage": [{"product":"Api","usagePercent":2.0}],
  41. "prepaidBalance": {"val": 12},
  42. "onDemandCap": {"val": 100},
  43. "onDemandUsed": {"val": 5},
  44. "isUnifiedBillingUser": true
  45. }
  46. }`)
  47. monthlyBody := []byte(`{
  48. "config": {
  49. "monthlyLimit": {"val": 15000},
  50. "used": {"val": 78},
  51. "billingPeriodStart": "2026-07-01T00:00:00Z",
  52. "billingPeriodEnd": "2026-08-01T00:00:00Z"
  53. }
  54. }`)
  55. weeklyPayload, err := ParseBillingPayload(weeklyBody)
  56. require.NoError(t, err)
  57. monthlyPayload, err := ParseBillingPayload(monthlyBody)
  58. require.NoError(t, err)
  59. weekly := BuildBillingSummary(weeklyPayload.Config)
  60. monthly := BuildBillingSummary(monthlyPayload.Config)
  61. require.NotNil(t, weekly)
  62. require.NotNil(t, monthly)
  63. require.Equal(t, "weekly", weekly.PeriodType)
  64. require.InDelta(t, 2.0, *weekly.UsagePercent, 1e-9)
  65. require.Equal(t, "Api", weekly.ProductUsage[0].Product)
  66. require.InDelta(t, 12, *weekly.PrepaidBalance, 1e-9)
  67. require.InDelta(t, 100, *weekly.OnDemandCap, 1e-9)
  68. require.InDelta(t, 5, *weekly.OnDemandUsed, 1e-9)
  69. require.True(t, weekly.IsUnifiedBillingUser)
  70. require.Equal(t, "SuperGrok", monthly.Plan)
  71. require.InDelta(t, 15000, *monthly.MonthlyLimitCents, 1e-9)
  72. require.InDelta(t, 78, *monthly.UsedCents, 1e-9)
  73. require.InDelta(t, 0.52, *monthly.UsedPercent, 1e-2)
  74. require.InDelta(t, 150, *monthly.MonthlyLimit, 1e-9)
  75. require.InDelta(t, 0.78, *monthly.MonthlyUsed, 1e-9)
  76. merged := MergeBillingProbeResult(nil, weekly, monthly, true, true)
  77. require.Equal(t, "weekly", merged.PeriodType)
  78. require.InDelta(t, 2.0, *merged.UsagePercent, 1e-9)
  79. require.Equal(t, "SuperGrok", merged.Plan)
  80. require.InDelta(t, 15000, *merged.MonthlyLimitCents, 1e-9)
  81. require.Equal(t, "2026-08-01T00:00:00Z", merged.BillingPeriodEnd)
  82. require.InDelta(t, 12, *merged.PrepaidBalance, 1e-9)
  83. require.InDelta(t, 100, *merged.OnDemandCap, 1e-9)
  84. require.InDelta(t, 150, *merged.MonthlyLimit, 1e-9)
  85. require.InDelta(t, 0.78, *merged.MonthlyUsed, 1e-9)
  86. }
  87. func TestParseCentValueBareNumber(t *testing.T) {
  88. t.Parallel()
  89. raw, _ := json.Marshal(15000)
  90. v := parseCentValue(raw)
  91. require.NotNil(t, v)
  92. require.InDelta(t, 15000, *v, 1e-9)
  93. }
  94. func TestBuildBillingSummaryMonthlyOnlyKeepsWeeklyUsageEmpty(t *testing.T) {
  95. t.Parallel()
  96. payload, err := ParseBillingPayload([]byte(`{"config":{"monthlyLimit":{"val":15000},"used":{"val":7500},"billingPeriodStart":"2026-07-01T00:00:00Z","billingPeriodEnd":"2026-08-01T00:00:00Z"}}`))
  97. require.NoError(t, err)
  98. summary := BuildBillingSummary(payload.Config)
  99. require.NotNil(t, summary)
  100. require.Equal(t, "monthly", summary.PeriodType)
  101. require.Nil(t, summary.UsagePercent)
  102. require.InDelta(t, 50, *summary.UsedPercent, 1e-9)
  103. }
  104. func TestBuildBillingSummaryWeeklyDoesNotInheritMonthlyPeriodEnd(t *testing.T) {
  105. t.Parallel()
  106. // Weekly usage without currentPeriod.end must not copy billingPeriodEnd (monthly).
  107. payload, err := ParseBillingPayload([]byte(`{
  108. "config": {
  109. "creditUsagePercent": 95.0,
  110. "productUsage": [{"product":"Api","usagePercent":95.0}],
  111. "billingPeriodStart": "2026-07-01T00:00:00Z",
  112. "billingPeriodEnd": "2026-08-01T00:00:00Z",
  113. "monthlyLimit": {"val": 15000},
  114. "used": {"val": 1000}
  115. }
  116. }`))
  117. require.NoError(t, err)
  118. summary := BuildBillingSummary(payload.Config)
  119. require.NotNil(t, summary)
  120. require.Equal(t, "weekly", summary.PeriodType)
  121. require.Equal(t, "", summary.PeriodEnd)
  122. require.Equal(t, "2026-08-01T00:00:00Z", summary.BillingPeriodEnd)
  123. }
  124. func TestMergeBillingProbeResultRetainsFailedWindow(t *testing.T) {
  125. t.Parallel()
  126. previous := &BillingSummary{
  127. PeriodType: "weekly",
  128. UsagePercent: floatPointer(100),
  129. PeriodEnd: "2026-07-16T00:00:00Z",
  130. MonthlyLimitCents: floatPointer(15000),
  131. UsedPercent: floatPointer(20),
  132. BillingPeriodEnd: "2026-08-01T00:00:00Z",
  133. WeeklyUpdatedAt: "2026-07-10T00:00:00Z",
  134. MonthlyUpdatedAt: "2026-07-10T00:00:00Z",
  135. FailedWindows: []string{"monthly"},
  136. }
  137. monthly := &BillingSummary{
  138. PeriodType: "monthly",
  139. MonthlyLimitCents: floatPointer(15000),
  140. UsedPercent: floatPointer(30),
  141. BillingPeriodEnd: "2026-08-01T00:00:00Z",
  142. }
  143. merged := MergeBillingProbeResult(previous, nil, monthly, false, true)
  144. require.Equal(t, "weekly", merged.PeriodType)
  145. require.InDelta(t, 100, *merged.UsagePercent, 1e-9)
  146. require.Equal(t, previous.WeeklyUpdatedAt, merged.WeeklyUpdatedAt)
  147. require.InDelta(t, 30, *merged.UsedPercent, 1e-9)
  148. require.NotEqual(t, previous.MonthlyUpdatedAt, merged.MonthlyUpdatedAt)
  149. require.True(t, merged.Partial)
  150. require.Equal(t, []string{"weekly"}, merged.FailedWindows)
  151. require.Equal(t, []string{"monthly"}, previous.FailedWindows)
  152. }
  153. func floatPointer(value float64) *float64 {
  154. return &value
  155. }