package dto import ( "encoding/json" "testing" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/stretchr/testify/require" ) func TestUsageLogFromService_IncludesOpenAIWSMode(t *testing.T) { t.Parallel() wsLog := &service.UsageLog{ RequestID: "req_1", Model: "gpt-5.3-codex", OpenAIWSMode: true, } httpLog := &service.UsageLog{ RequestID: "resp_1", Model: "gpt-5.3-codex", OpenAIWSMode: false, } require.True(t, UsageLogFromService(wsLog).OpenAIWSMode) require.False(t, UsageLogFromService(httpLog).OpenAIWSMode) require.True(t, UsageLogFromServiceAdmin(wsLog).OpenAIWSMode) require.False(t, UsageLogFromServiceAdmin(httpLog).OpenAIWSMode) } func TestUsageLogFromService_PrefersRequestTypeForLegacyFields(t *testing.T) { t.Parallel() log := &service.UsageLog{ RequestID: "req_2", Model: "gpt-5.3-codex", RequestType: service.RequestTypeWSV2, Stream: false, OpenAIWSMode: false, } userDTO := UsageLogFromService(log) adminDTO := UsageLogFromServiceAdmin(log) require.Equal(t, "ws_v2", userDTO.RequestType) require.True(t, userDTO.Stream) require.True(t, userDTO.OpenAIWSMode) require.Equal(t, "ws_v2", adminDTO.RequestType) require.True(t, adminDTO.Stream) require.True(t, adminDTO.OpenAIWSMode) } func TestUsageCleanupTaskFromService_RequestTypeMapping(t *testing.T) { t.Parallel() requestType := int16(service.RequestTypeStream) task := &service.UsageCleanupTask{ ID: 1, Status: service.UsageCleanupStatusPending, Filters: service.UsageCleanupFilters{ RequestType: &requestType, }, } dtoTask := UsageCleanupTaskFromService(task) require.NotNil(t, dtoTask) require.NotNil(t, dtoTask.Filters.RequestType) require.Equal(t, "stream", *dtoTask.Filters.RequestType) } func TestRequestTypeStringPtrNil(t *testing.T) { t.Parallel() require.Nil(t, requestTypeStringPtr(nil)) } func TestUsageLogFromService_IncludesServiceTierForUserAndAdmin(t *testing.T) { t.Parallel() serviceTier := "priority" inboundEndpoint := "/v1/chat/completions" upstreamEndpoint := "/v1/responses" log := &service.UsageLog{ RequestID: "req_3", Model: "gpt-5.4", ServiceTier: &serviceTier, InboundEndpoint: &inboundEndpoint, UpstreamEndpoint: &upstreamEndpoint, AccountRateMultiplier: f64Ptr(1.5), } userDTO := UsageLogFromService(log) adminDTO := UsageLogFromServiceAdmin(log) require.NotNil(t, userDTO.ServiceTier) require.Equal(t, serviceTier, *userDTO.ServiceTier) require.NotNil(t, userDTO.InboundEndpoint) require.Equal(t, inboundEndpoint, *userDTO.InboundEndpoint) require.NotNil(t, userDTO.UpstreamEndpoint) require.Equal(t, upstreamEndpoint, *userDTO.UpstreamEndpoint) require.NotNil(t, adminDTO.ServiceTier) require.Equal(t, serviceTier, *adminDTO.ServiceTier) require.NotNil(t, adminDTO.InboundEndpoint) require.Equal(t, inboundEndpoint, *adminDTO.InboundEndpoint) require.NotNil(t, adminDTO.UpstreamEndpoint) require.Equal(t, upstreamEndpoint, *adminDTO.UpstreamEndpoint) require.NotNil(t, adminDTO.AccountRateMultiplier) require.InDelta(t, 1.5, *adminDTO.AccountRateMultiplier, 1e-12) } func TestUsageLogFromService_UsesRequestedModelAndKeepsUpstreamAdminOnly(t *testing.T) { t.Parallel() upstreamModel := "claude-sonnet-4-20250514" log := &service.UsageLog{ RequestID: "req_4", Model: upstreamModel, RequestedModel: "claude-sonnet-4", UpstreamModel: &upstreamModel, } userDTO := UsageLogFromService(log) adminDTO := UsageLogFromServiceAdmin(log) require.Equal(t, "claude-sonnet-4", userDTO.Model) require.Equal(t, "claude-sonnet-4", adminDTO.Model) userJSON, err := json.Marshal(userDTO) require.NoError(t, err) require.NotContains(t, string(userJSON), "upstream_model") adminJSON, err := json.Marshal(adminDTO) require.NoError(t, err) require.Contains(t, string(adminJSON), `"upstream_model":"claude-sonnet-4-20250514"`) } func TestUsageLogFromService_FallsBackToLegacyModelWhenRequestedModelMissing(t *testing.T) { t.Parallel() log := &service.UsageLog{ RequestID: "req_legacy", Model: "claude-3", } userDTO := UsageLogFromService(log) adminDTO := UsageLogFromServiceAdmin(log) require.Equal(t, "claude-3", userDTO.Model) require.Equal(t, "claude-3", adminDTO.Model) } func f64Ptr(value float64) *float64 { return &value }