Files
sub2api/backend/internal/handler/admin/available_channel_handler_test.go
erio 365ef1fdf7 refactor(channels): consolidate pricing index, tighten types, polish DTOs
Follow-up to the available-channels review pass. No behavior change for
end users; tightens internals based on three independent code reviews.

Backend
- service/channel.go: collapse buildPricingLookup + pricedNamesFor
  into a single platformPricingIndex (byLower + originalCase + ordered
  names), built once per SupportedModels call. Fixes a casing-
  consistency bug where the same logical model appeared with mapping
  case in the exact branch but pricing case in the wildcard branch —
  pricing's original case now wins everywhere.
- service/channel.go: doc that a mapping key of just "*" expands to
  every priced model on the platform (intentional "passthrough all").
- service/channel_available.go: normalize empty BillingModelSource to
  channel_mapped at construction time, removing the same fallback
  duplicated in the admin DTO mapper and the admin Vue template.
- handler/admin/available_channel_handler.go: unexport
  availableChannelToAdminResponse (same-package usage only); mapper
  is now a pure passthrough.
- handler/available_channel_handler.go: drop the middleware2 alias
  (no name collision in this file).

Frontend
- utils/pricing.ts: extract formatScaled, used by SupportedModelChip
  and PricingRow.
- api/admin/channels.ts: re-export BillingMode from constants/channel;
  tighten Channel.status / billing_model_source to ChannelStatus /
  BillingModelSource (and same for AvailableChannel).
- components/channels/AvailableChannelsTable.vue: drop dead
  withDefaults wrapper (loading is required, both call sites pass it).
- views/admin/AvailableChannelsView.vue: drop the redundant
  || BILLING_MODEL_SOURCE_CHANNEL_MAPPED fallback (now applied in
  service layer); remove unused import.
- i18n zh + en: delete unused tierLabel and tokenRange keys from
  both availableChannels.pricing and admin.availableChannels.pricing.

Tests
- New: SupportedModels_ExactKeyUsesPricedCaseWhenAvailable locks the
  pricing-case-wins rule.
- New: SupportedModels_AsteriskOnlyMappingExpandsAllPriced documents
  the "*" expansion rule.
- Admin handler: existing tests adjusted to pass an explicit
  BillingModelSource (default-fill is now exercised by service tests).
2026-04-21 01:05:14 +08:00

58 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build unit
package admin
import (
"encoding/json"
"testing"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/stretchr/testify/require"
)
func TestAvailableChannelToAdminResponse_IncludesFullDTO(t *testing.T) {
// 管理员视图应包含 id / status / billing_model_source / restrict_models 等
// 管理字段mapper 是纯透传BillingModelSource 的默认回填由 service 层负责。
input := service.AvailableChannel{
ID: 42,
Name: "ch",
Description: "d",
Status: service.StatusActive,
BillingModelSource: service.BillingModelSourceChannelMapped,
RestrictModels: true,
Groups: []service.AvailableGroupRef{
{ID: 1, Name: "g1", Platform: "anthropic"},
},
SupportedModels: []service.SupportedModel{
{Name: "claude-sonnet-4-6", Platform: "anthropic"},
},
}
resp := availableChannelToAdminResponse(input)
require.Equal(t, int64(42), resp.ID)
require.Equal(t, "ch", resp.Name)
require.Equal(t, service.StatusActive, resp.Status)
require.Equal(t, service.BillingModelSourceChannelMapped, resp.BillingModelSource)
require.True(t, resp.RestrictModels)
require.Len(t, resp.Groups, 1)
require.Len(t, resp.SupportedModels, 1)
// JSON 层验证管理字段确实会被序列化。
raw, err := json.Marshal(resp)
require.NoError(t, err)
var decoded map[string]any
require.NoError(t, json.Unmarshal(raw, &decoded))
for _, key := range []string{"id", "status", "billing_model_source", "restrict_models", "groups", "supported_models"} {
_, exists := decoded[key]
require.Truef(t, exists, "admin DTO must expose %q", key)
}
}
func TestAvailableChannelToAdminResponse_PreservesExplicitBillingSource(t *testing.T) {
input := service.AvailableChannel{
BillingModelSource: service.BillingModelSourceUpstream,
}
resp := availableChannelToAdminResponse(input)
require.Equal(t, service.BillingModelSourceUpstream, resp.BillingModelSource)
}