ops_openai_token_stats_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package service
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  7. "github.com/stretchr/testify/require"
  8. )
  9. type openAITokenStatsRepoStub struct {
  10. OpsRepository
  11. resp *OpsOpenAITokenStatsResponse
  12. err error
  13. captured *OpsOpenAITokenStatsFilter
  14. }
  15. func (s *openAITokenStatsRepoStub) GetOpenAITokenStats(ctx context.Context, filter *OpsOpenAITokenStatsFilter) (*OpsOpenAITokenStatsResponse, error) {
  16. s.captured = filter
  17. if s.err != nil {
  18. return nil, s.err
  19. }
  20. if s.resp != nil {
  21. return s.resp, nil
  22. }
  23. return &OpsOpenAITokenStatsResponse{}, nil
  24. }
  25. func TestOpsServiceGetOpenAITokenStats_Validation(t *testing.T) {
  26. now := time.Now().UTC()
  27. tests := []struct {
  28. name string
  29. filter *OpsOpenAITokenStatsFilter
  30. wantCode int
  31. wantReason string
  32. }{
  33. {
  34. name: "filter 不能为空",
  35. filter: nil,
  36. wantCode: 400,
  37. wantReason: "OPS_FILTER_REQUIRED",
  38. },
  39. {
  40. name: "start_time/end_time 必填",
  41. filter: &OpsOpenAITokenStatsFilter{
  42. StartTime: time.Time{},
  43. EndTime: now,
  44. },
  45. wantCode: 400,
  46. wantReason: "OPS_TIME_RANGE_REQUIRED",
  47. },
  48. {
  49. name: "start_time 不能晚于 end_time",
  50. filter: &OpsOpenAITokenStatsFilter{
  51. StartTime: now,
  52. EndTime: now.Add(-1 * time.Minute),
  53. },
  54. wantCode: 400,
  55. wantReason: "OPS_TIME_RANGE_INVALID",
  56. },
  57. {
  58. name: "group_id 必须大于 0",
  59. filter: &OpsOpenAITokenStatsFilter{
  60. StartTime: now.Add(-time.Hour),
  61. EndTime: now,
  62. GroupID: int64Ptr(0),
  63. },
  64. wantCode: 400,
  65. wantReason: "OPS_GROUP_ID_INVALID",
  66. },
  67. {
  68. name: "top_n 与分页参数互斥",
  69. filter: &OpsOpenAITokenStatsFilter{
  70. StartTime: now.Add(-time.Hour),
  71. EndTime: now,
  72. TopN: 10,
  73. Page: 1,
  74. },
  75. wantCode: 400,
  76. wantReason: "OPS_PAGINATION_CONFLICT",
  77. },
  78. {
  79. name: "top_n 参数越界",
  80. filter: &OpsOpenAITokenStatsFilter{
  81. StartTime: now.Add(-time.Hour),
  82. EndTime: now,
  83. TopN: 101,
  84. },
  85. wantCode: 400,
  86. wantReason: "OPS_TOPN_INVALID",
  87. },
  88. {
  89. name: "page_size 参数越界",
  90. filter: &OpsOpenAITokenStatsFilter{
  91. StartTime: now.Add(-time.Hour),
  92. EndTime: now,
  93. Page: 1,
  94. PageSize: 101,
  95. },
  96. wantCode: 400,
  97. wantReason: "OPS_PAGE_SIZE_INVALID",
  98. },
  99. }
  100. for _, tt := range tests {
  101. t.Run(tt.name, func(t *testing.T) {
  102. svc := &OpsService{
  103. opsRepo: &openAITokenStatsRepoStub{},
  104. }
  105. _, err := svc.GetOpenAITokenStats(context.Background(), tt.filter)
  106. require.Error(t, err)
  107. require.Equal(t, tt.wantCode, infraerrors.Code(err))
  108. require.Equal(t, tt.wantReason, infraerrors.Reason(err))
  109. })
  110. }
  111. }
  112. func TestOpsServiceGetOpenAITokenStats_DefaultPagination(t *testing.T) {
  113. now := time.Now().UTC()
  114. repo := &openAITokenStatsRepoStub{
  115. resp: &OpsOpenAITokenStatsResponse{
  116. Items: []*OpsOpenAITokenStatsItem{
  117. {Model: "gpt-4o-mini", RequestCount: 10},
  118. },
  119. Total: 1,
  120. },
  121. }
  122. svc := &OpsService{opsRepo: repo}
  123. filter := &OpsOpenAITokenStatsFilter{
  124. TimeRange: "30d",
  125. StartTime: now.Add(-30 * 24 * time.Hour),
  126. EndTime: now,
  127. }
  128. resp, err := svc.GetOpenAITokenStats(context.Background(), filter)
  129. require.NoError(t, err)
  130. require.NotNil(t, resp)
  131. require.NotNil(t, repo.captured)
  132. require.Equal(t, 1, repo.captured.Page)
  133. require.Equal(t, 20, repo.captured.PageSize)
  134. require.Equal(t, 0, repo.captured.TopN)
  135. }
  136. func TestOpsServiceGetOpenAITokenStats_RepoUnavailable(t *testing.T) {
  137. now := time.Now().UTC()
  138. svc := &OpsService{}
  139. _, err := svc.GetOpenAITokenStats(context.Background(), &OpsOpenAITokenStatsFilter{
  140. TimeRange: "1h",
  141. StartTime: now.Add(-time.Hour),
  142. EndTime: now,
  143. TopN: 10,
  144. })
  145. require.Error(t, err)
  146. require.Equal(t, 503, infraerrors.Code(err))
  147. require.Equal(t, "OPS_REPO_UNAVAILABLE", infraerrors.Reason(err))
  148. }
  149. func int64Ptr(v int64) *int64 { return &v }