grok_media_video_billing_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/stretchr/testify/require"
  6. )
  7. func TestGrokVideoE2EDurationFromCreatedAt(t *testing.T) {
  8. t.Parallel()
  9. created := time.Now().UTC().Add(-45 * time.Second)
  10. d := GrokVideoE2EDuration(created.Format(time.RFC3339Nano), time.Now().UTC())
  11. require.GreaterOrEqual(t, d, 44*time.Second)
  12. require.LessOrEqual(t, d, 47*time.Second)
  13. require.Equal(t, time.Duration(0), GrokVideoE2EDuration("", time.Now()))
  14. require.Equal(t, time.Duration(0), GrokVideoE2EDuration("not-a-time", time.Now()))
  15. // Future CreatedAt clamps to zero (clock skew).
  16. require.Equal(t, time.Duration(0), GrokVideoE2EDuration(time.Now().Add(time.Hour).UTC().Format(time.RFC3339Nano), time.Now()))
  17. }
  18. func TestGrokVideoPendingCreatedAtStampOnStoreShape(t *testing.T) {
  19. t.Parallel()
  20. // GrokVideoPendingCreatedAtNow must be parseable by GrokVideoE2EDuration.
  21. stamp := GrokVideoPendingCreatedAtNow()
  22. require.NotEmpty(t, stamp)
  23. d := GrokVideoE2EDuration(stamp, time.Now().UTC().Add(2*time.Second))
  24. require.GreaterOrEqual(t, d, time.Second)
  25. require.LessOrEqual(t, d, 3*time.Second)
  26. }
  27. func TestIsGrokVideoStatusBillable(t *testing.T) {
  28. t.Parallel()
  29. // Official success: status=done + video.url
  30. require.True(t, IsGrokVideoStatusBillable([]byte(`{
  31. "status":"done",
  32. "model":"grok-imagine-video-1.5",
  33. "video":{"url":"https://vidgen.x.ai/x.mp4","duration":8,"respect_moderation":true}
  34. }`)))
  35. // Official non-success states
  36. require.False(t, IsGrokVideoStatusBillable(nil))
  37. require.False(t, IsGrokVideoStatusBillable([]byte(`{"status":"pending"}`)))
  38. require.False(t, IsGrokVideoStatusBillable([]byte(`{"status":"expired"}`)))
  39. require.False(t, IsGrokVideoStatusBillable([]byte(`{"status":"failed"}`)))
  40. // done without video.url is not billable
  41. require.False(t, IsGrokVideoStatusBillable([]byte(`{"status":"done"}`)))
  42. // URL alone (legacy/non-official shapes) is not enough
  43. require.False(t, IsGrokVideoStatusBillable([]byte(`{"url":"https://example.com/v.mp4"}`)))
  44. require.False(t, IsGrokVideoStatusBillable([]byte(`{"download_url":"/v1/videos/task/content"}`)))
  45. // "completed" is not the official enum value
  46. require.False(t, IsGrokVideoStatusBillable([]byte(`{"status":"completed","video":{"url":"https://vidgen.x.ai/x.mp4"}}`)))
  47. }
  48. func TestExtractGrokVideoBillingFromStatusBodyPrefersUpstreamParams(t *testing.T) {
  49. t.Parallel()
  50. pending := &GrokVideoPendingBilling{
  51. Model: "pending-model",
  52. BillingModel: "pending-billing",
  53. UpstreamModel: "pending-upstream",
  54. VideoResolution: VideoBillingResolution720P,
  55. VideoDurationSeconds: 8,
  56. }
  57. // Official completed body from docs.x.ai Video Generation.
  58. body := []byte(`{
  59. "status":"done",
  60. "model":"grok-imagine-video-1.5",
  61. "video":{"url":"https://vidgen.x.ai/signed.mp4","duration":12,"respect_moderation":true}
  62. }`)
  63. result := ExtractGrokVideoBillingFromStatusBody(body, pending, "req-1")
  64. require.NotNil(t, result)
  65. require.Equal(t, 1, result.VideoCount)
  66. require.Equal(t, "grok-imagine-video-1.5", result.Model)
  67. // Resolution is not in official status response — use create-time request.
  68. require.Equal(t, VideoBillingResolution720P, result.VideoResolution)
  69. // Duration prefers official video.duration.
  70. require.Equal(t, 12, result.VideoDurationSeconds)
  71. }
  72. func TestExtractGrokVideoBillingFromStatusBodyFallsBackToPending(t *testing.T) {
  73. t.Parallel()
  74. pending := &GrokVideoPendingBilling{
  75. Model: "create-model",
  76. BillingModel: "create-billing",
  77. UpstreamModel: "create-upstream",
  78. VideoResolution: VideoBillingResolution1080P,
  79. VideoDurationSeconds: 10,
  80. }
  81. // done + video.url, but no model/duration in body.
  82. body := []byte(`{"status":"done","video":{"url":"https://vidgen.x.ai/signed.mp4"}}`)
  83. result := ExtractGrokVideoBillingFromStatusBody(body, pending, "req-2")
  84. require.NotNil(t, result)
  85. require.Equal(t, "create-billing", result.BillingModel)
  86. require.Equal(t, "create-upstream", result.UpstreamModel)
  87. require.Equal(t, VideoBillingResolution1080P, result.VideoResolution)
  88. require.Equal(t, 10, result.VideoDurationSeconds)
  89. }
  90. func TestExtractGrokVideoBillingRejectsNonDoneStatus(t *testing.T) {
  91. t.Parallel()
  92. pending := &GrokVideoPendingBilling{Model: "m", VideoDurationSeconds: 8, VideoResolution: "720p"}
  93. require.Nil(t, ExtractGrokVideoBillingFromStatusBody(
  94. []byte(`{"status":"pending","video":{"url":"https://vidgen.x.ai/x.mp4","duration":8}}`),
  95. pending, "req",
  96. ))
  97. require.Nil(t, ExtractGrokVideoBillingFromStatusBody(
  98. []byte(`{"status":"completed","video":{"url":"https://vidgen.x.ai/x.mp4","duration":8}}`),
  99. pending, "req",
  100. ))
  101. }
  102. func TestGrokMediaUsageFromResponseVideoCreateDoesNotBill(t *testing.T) {
  103. t.Parallel()
  104. info := GrokMediaRequestInfo{Model: "grok-imagine-video", Resolution: "720p", DurationSeconds: 10}
  105. meta := grokMediaUsageFromResponse(GrokMediaEndpointVideosGenerations, info, []byte(`{"request_id":"v1"}`))
  106. require.Equal(t, "v1", meta.ResponseID)
  107. require.Equal(t, 0, meta.VideoCount)
  108. require.Equal(t, 10, meta.VideoDurationSeconds)
  109. require.Equal(t, VideoBillingResolution720P, meta.VideoResolution)
  110. }
  111. func TestGrokMediaUsageFromResponseVideoStatusBillsOnOfficialDone(t *testing.T) {
  112. t.Parallel()
  113. meta := grokMediaUsageFromResponse(
  114. GrokMediaEndpointVideoStatus,
  115. GrokMediaRequestInfo{},
  116. []byte(`{"status":"done","model":"grok-imagine-video-1.5","video":{"url":"https://vidgen.x.ai/a.mp4","duration":9}}`),
  117. )
  118. require.Equal(t, 1, meta.VideoCount)
  119. require.Equal(t, 9, meta.VideoDurationSeconds)
  120. require.Equal(t, "grok-imagine-video-1.5", meta.Model)
  121. // Official non-done must not set billable units.
  122. pendingOnly := grokMediaUsageFromResponse(
  123. GrokMediaEndpointVideoStatus,
  124. GrokMediaRequestInfo{},
  125. []byte(`{"status":"pending"}`),
  126. )
  127. require.Equal(t, 0, pendingOnly.VideoCount)
  128. // completed is not official done.
  129. completed := grokMediaUsageFromResponse(
  130. GrokMediaEndpointVideoStatus,
  131. GrokMediaRequestInfo{},
  132. []byte(`{"status":"completed","video":{"url":"https://vidgen.x.ai/a.mp4","duration":9}}`),
  133. )
  134. require.Equal(t, 0, completed.VideoCount)
  135. }