mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-02 22:42:14 +08:00
Cover IsValidModelSource/NormalizeModelSource, resolveModelDimensionExpression SQL expressions, invalid model_source 400 responses on both GetModelStats and GetUserBreakdown, upstream_model in scan/insert SQL mock expectations, and updated passthrough/billing test signatures.
230 lines
7.3 KiB
Go
230 lines
7.3 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// --- mock repo ---
|
|
|
|
type userBreakdownRepoCapture struct {
|
|
service.UsageLogRepository
|
|
capturedDim usagestats.UserBreakdownDimension
|
|
capturedLimit int
|
|
result []usagestats.UserBreakdownItem
|
|
}
|
|
|
|
func (r *userBreakdownRepoCapture) GetUserBreakdownStats(
|
|
_ context.Context, _, _ time.Time,
|
|
dim usagestats.UserBreakdownDimension, limit int,
|
|
) ([]usagestats.UserBreakdownItem, error) {
|
|
r.capturedDim = dim
|
|
r.capturedLimit = limit
|
|
if r.result != nil {
|
|
return r.result, nil
|
|
}
|
|
return []usagestats.UserBreakdownItem{}, nil
|
|
}
|
|
|
|
func newUserBreakdownRouter(repo *userBreakdownRepoCapture) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
svc := service.NewDashboardService(repo, nil, nil, nil)
|
|
h := NewDashboardHandler(svc, nil)
|
|
router := gin.New()
|
|
router.GET("/admin/dashboard/user-breakdown", h.GetUserBreakdown)
|
|
return router
|
|
}
|
|
|
|
// --- tests ---
|
|
|
|
func TestGetUserBreakdown_GroupIDFilter(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=42", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, int64(42), repo.capturedDim.GroupID)
|
|
require.Empty(t, repo.capturedDim.Model)
|
|
require.Empty(t, repo.capturedDim.Endpoint)
|
|
require.Equal(t, 50, repo.capturedLimit) // default limit
|
|
}
|
|
|
|
func TestGetUserBreakdown_ModelFilter(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=claude-opus-4-6", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, "claude-opus-4-6", repo.capturedDim.Model)
|
|
require.Equal(t, usagestats.ModelSourceRequested, repo.capturedDim.ModelType)
|
|
require.Equal(t, int64(0), repo.capturedDim.GroupID)
|
|
}
|
|
|
|
func TestGetUserBreakdown_ModelSourceFilter(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=claude-opus-4-6&model_source=upstream", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, usagestats.ModelSourceUpstream, repo.capturedDim.ModelType)
|
|
}
|
|
|
|
func TestGetUserBreakdown_InvalidModelSource(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model_source=foobar", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestGetUserBreakdown_EndpointFilter(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&endpoint=/v1/messages&endpoint_type=upstream", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, "/v1/messages", repo.capturedDim.Endpoint)
|
|
require.Equal(t, "upstream", repo.capturedDim.EndpointType)
|
|
}
|
|
|
|
func TestGetUserBreakdown_DefaultEndpointType(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&endpoint=/chat", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, "inbound", repo.capturedDim.EndpointType)
|
|
}
|
|
|
|
func TestGetUserBreakdown_CustomLimit(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=test&limit=100", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, 100, repo.capturedLimit)
|
|
}
|
|
|
|
func TestGetUserBreakdown_LimitClamped(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
// limit > 200 should fall back to default 50
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=test&limit=999", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, 50, repo.capturedLimit)
|
|
}
|
|
|
|
func TestGetUserBreakdown_ResponseFormat(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{
|
|
result: []usagestats.UserBreakdownItem{
|
|
{UserID: 1, Email: "alice@test.com", Requests: 100, TotalTokens: 50000, Cost: 1.5, ActualCost: 1.2},
|
|
{UserID: 2, Email: "bob@test.com", Requests: 50, TotalTokens: 25000, Cost: 0.8, ActualCost: 0.6},
|
|
},
|
|
}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=1", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var resp struct {
|
|
Code int `json:"code"`
|
|
Data struct {
|
|
Users []usagestats.UserBreakdownItem `json:"users"`
|
|
StartDate string `json:"start_date"`
|
|
EndDate string `json:"end_date"`
|
|
} `json:"data"`
|
|
}
|
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, resp.Code)
|
|
require.Len(t, resp.Data.Users, 2)
|
|
require.Equal(t, int64(1), resp.Data.Users[0].UserID)
|
|
require.Equal(t, "alice@test.com", resp.Data.Users[0].Email)
|
|
require.Equal(t, int64(100), resp.Data.Users[0].Requests)
|
|
require.InDelta(t, 1.2, resp.Data.Users[0].ActualCost, 0.001)
|
|
require.Equal(t, "2026-03-01", resp.Data.StartDate)
|
|
require.Equal(t, "2026-03-16", resp.Data.EndDate)
|
|
}
|
|
|
|
func TestGetUserBreakdown_EmptyResult(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=999", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var resp struct {
|
|
Data struct {
|
|
Users []usagestats.UserBreakdownItem `json:"users"`
|
|
} `json:"data"`
|
|
}
|
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
|
require.NoError(t, err)
|
|
require.Empty(t, resp.Data.Users)
|
|
}
|
|
|
|
func TestGetUserBreakdown_NoFilters(t *testing.T) {
|
|
repo := &userBreakdownRepoCapture{}
|
|
router := newUserBreakdownRouter(repo)
|
|
|
|
req := httptest.NewRequest(http.MethodGet,
|
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, int64(0), repo.capturedDim.GroupID)
|
|
require.Empty(t, repo.capturedDim.Model)
|
|
require.Empty(t, repo.capturedDim.Endpoint)
|
|
}
|