ops_repo_openai_token_stats_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package repository
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/DATA-DOG/go-sqlmock"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestOpsRepositoryGetOpenAITokenStats_PaginationMode(t *testing.T) {
  11. db, mock := newSQLMock(t)
  12. repo := &opsRepository{db: db}
  13. start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
  14. end := start.Add(24 * time.Hour)
  15. groupID := int64(9)
  16. filter := &service.OpsOpenAITokenStatsFilter{
  17. TimeRange: "1d",
  18. StartTime: start,
  19. EndTime: end,
  20. Platform: " OpenAI ",
  21. GroupID: &groupID,
  22. Page: 2,
  23. PageSize: 10,
  24. }
  25. mock.ExpectQuery(`SELECT COUNT\(\*\) FROM stats`).
  26. WithArgs(start, end, groupID, "openai").
  27. WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(3)))
  28. rows := sqlmock.NewRows([]string{
  29. "model",
  30. "request_count",
  31. "avg_tokens_per_sec",
  32. "avg_first_token_ms",
  33. "total_output_tokens",
  34. "avg_duration_ms",
  35. "requests_with_first_token",
  36. }).
  37. AddRow("gpt-4o-mini", int64(20), 21.56, 120.34, int64(3000), int64(850), int64(18)).
  38. AddRow("gpt-4.1", int64(20), 10.2, 240.0, int64(2500), int64(900), int64(20))
  39. mock.ExpectQuery(`ORDER BY request_count DESC, model ASC\s+LIMIT \$5 OFFSET \$6`).
  40. WithArgs(start, end, groupID, "openai", 10, 10).
  41. WillReturnRows(rows)
  42. resp, err := repo.GetOpenAITokenStats(context.Background(), filter)
  43. require.NoError(t, err)
  44. require.NotNil(t, resp)
  45. require.Equal(t, int64(3), resp.Total)
  46. require.Equal(t, 2, resp.Page)
  47. require.Equal(t, 10, resp.PageSize)
  48. require.Nil(t, resp.TopN)
  49. require.Equal(t, "openai", resp.Platform)
  50. require.NotNil(t, resp.GroupID)
  51. require.Equal(t, groupID, *resp.GroupID)
  52. require.Len(t, resp.Items, 2)
  53. require.Equal(t, "gpt-4o-mini", resp.Items[0].Model)
  54. require.NotNil(t, resp.Items[0].AvgTokensPerSec)
  55. require.InDelta(t, 21.56, *resp.Items[0].AvgTokensPerSec, 0.0001)
  56. require.NotNil(t, resp.Items[0].AvgFirstTokenMs)
  57. require.InDelta(t, 120.34, *resp.Items[0].AvgFirstTokenMs, 0.0001)
  58. require.NoError(t, mock.ExpectationsWereMet())
  59. }
  60. func TestOpsRepositoryGetOpenAITokenStats_TopNMode(t *testing.T) {
  61. db, mock := newSQLMock(t)
  62. repo := &opsRepository{db: db}
  63. start := time.Date(2026, 1, 1, 10, 0, 0, 0, time.UTC)
  64. end := start.Add(time.Hour)
  65. filter := &service.OpsOpenAITokenStatsFilter{
  66. TimeRange: "1h",
  67. StartTime: start,
  68. EndTime: end,
  69. TopN: 5,
  70. }
  71. mock.ExpectQuery(`SELECT COUNT\(\*\) FROM stats`).
  72. WithArgs(start, end).
  73. WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(1)))
  74. rows := sqlmock.NewRows([]string{
  75. "model",
  76. "request_count",
  77. "avg_tokens_per_sec",
  78. "avg_first_token_ms",
  79. "total_output_tokens",
  80. "avg_duration_ms",
  81. "requests_with_first_token",
  82. }).
  83. AddRow("gpt-4o", int64(5), nil, nil, int64(0), int64(0), int64(0))
  84. mock.ExpectQuery(`ORDER BY request_count DESC, model ASC\s+LIMIT \$3`).
  85. WithArgs(start, end, 5).
  86. WillReturnRows(rows)
  87. resp, err := repo.GetOpenAITokenStats(context.Background(), filter)
  88. require.NoError(t, err)
  89. require.NotNil(t, resp)
  90. require.NotNil(t, resp.TopN)
  91. require.Equal(t, 5, *resp.TopN)
  92. require.Equal(t, 0, resp.Page)
  93. require.Equal(t, 0, resp.PageSize)
  94. require.Len(t, resp.Items, 1)
  95. require.Nil(t, resp.Items[0].AvgTokensPerSec)
  96. require.Nil(t, resp.Items[0].AvgFirstTokenMs)
  97. require.NoError(t, mock.ExpectationsWereMet())
  98. }
  99. func TestOpsRepositoryGetOpenAITokenStats_EmptyResult(t *testing.T) {
  100. db, mock := newSQLMock(t)
  101. repo := &opsRepository{db: db}
  102. start := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
  103. end := start.Add(30 * time.Minute)
  104. filter := &service.OpsOpenAITokenStatsFilter{
  105. TimeRange: "30m",
  106. StartTime: start,
  107. EndTime: end,
  108. Page: 1,
  109. PageSize: 20,
  110. }
  111. mock.ExpectQuery(`SELECT COUNT\(\*\) FROM stats`).
  112. WithArgs(start, end).
  113. WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(0)))
  114. mock.ExpectQuery(`ORDER BY request_count DESC, model ASC\s+LIMIT \$3 OFFSET \$4`).
  115. WithArgs(start, end, 20, 0).
  116. WillReturnRows(sqlmock.NewRows([]string{
  117. "model",
  118. "request_count",
  119. "avg_tokens_per_sec",
  120. "avg_first_token_ms",
  121. "total_output_tokens",
  122. "avg_duration_ms",
  123. "requests_with_first_token",
  124. }))
  125. resp, err := repo.GetOpenAITokenStats(context.Background(), filter)
  126. require.NoError(t, err)
  127. require.NotNil(t, resp)
  128. require.Equal(t, int64(0), resp.Total)
  129. require.Len(t, resp.Items, 0)
  130. require.Equal(t, 1, resp.Page)
  131. require.Equal(t, 20, resp.PageSize)
  132. require.NoError(t, mock.ExpectationsWereMet())
  133. }