- DEFAULT_CHAIN_ADMISSION.md: reviewed and approved, real artifact refs added - DEFAULT_DATA_IDEMPOTENT_RELEASE_GATE.md: reviewed and approved - scripts/setup_default_data.sh: idempotent init with --dry-run/--apply/artifact - scripts/test/test_default_data.sh: 4 test cases all pass - scripts/acceptance/verify_user_key_self_service.sh: Phase 0 skeleton - .gitignore: add generated artifact directories
252 lines
8.7 KiB
Go
252 lines
8.7 KiB
Go
package provision
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/host/sub2api"
|
|
"sub2api-cn-relay-manager/internal/pack"
|
|
)
|
|
|
|
func TestBuildModelPoolFiltersUnsupportedRoutesAndSortsByPriority(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
providerA := sampleProviderManifest()
|
|
providerA.ProviderID = "deepseek-official"
|
|
providerA.DisplayName = "DeepSeek Official"
|
|
providerA.BaseURL = "https://api.deepseek.com/v1"
|
|
|
|
providerB := sampleProviderManifest()
|
|
providerB.ProviderID = "deepseek-backup"
|
|
providerB.DisplayName = "DeepSeek Backup"
|
|
providerB.BaseURL = "https://backup.deepseek.example.com/v1"
|
|
|
|
providerC := sampleProviderManifest()
|
|
providerC.ProviderID = "deepseek-bad"
|
|
providerC.DisplayName = "DeepSeek Bad"
|
|
providerC.BaseURL = "https://bad.deepseek.example.com/v1"
|
|
|
|
pool, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "deepseek-chat",
|
|
Candidates: []ModelPoolCandidate{
|
|
{
|
|
RouteID: "route-backup",
|
|
Provider: providerB,
|
|
Priority: 20,
|
|
Inventory: capabilityInventoryWithSupport("deepseek-chat", "deepseek-chat", sub2api.SupportLevelDirect, true, true),
|
|
},
|
|
{
|
|
RouteID: "route-primary",
|
|
Provider: providerA,
|
|
Priority: 10,
|
|
Inventory: capabilityInventoryWithSupport("deepseek-chat", "deepseek-chat", sub2api.SupportLevelDirect, true, true),
|
|
},
|
|
{
|
|
RouteID: "route-bad",
|
|
Provider: providerC,
|
|
Priority: 5,
|
|
Inventory: capabilityInventoryWithSupport("deepseek-chat", "deepseek-chat", sub2api.SupportLevelUpstreamUnhealthy, true, false),
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildModelPool() error = %v", err)
|
|
}
|
|
if len(pool.Routes) != 2 {
|
|
t.Fatalf("len(pool.Routes) = %d, want 2", len(pool.Routes))
|
|
}
|
|
if pool.Routes[0].RouteID != "route-primary" || pool.Routes[1].RouteID != "route-backup" {
|
|
t.Fatalf("route order = [%s %s], want [route-primary route-backup]", pool.Routes[0].RouteID, pool.Routes[1].RouteID)
|
|
}
|
|
if pool.Routes[0].SupportLevel != sub2api.SupportLevelDirect {
|
|
t.Fatalf("pool.Routes[0].SupportLevel = %q, want %q", pool.Routes[0].SupportLevel, sub2api.SupportLevelDirect)
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolPreservesAdvertisedAndCallableModelDifference(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
provider := sampleProviderManifest()
|
|
provider.ProviderID = "deepseek-alias"
|
|
provider.DisplayName = "DeepSeek Alias"
|
|
provider.BaseURL = "https://alias.deepseek.example.com/v1"
|
|
|
|
pool, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "deepseek-chat",
|
|
Candidates: []ModelPoolCandidate{
|
|
{
|
|
RouteID: "route-alias",
|
|
Provider: provider,
|
|
Priority: 10,
|
|
AdvertisedModel: "deepseek-v3",
|
|
CallableModel: "deepseek-chat",
|
|
Inventory: capabilityInventoryWithSupport("deepseek-chat", "deepseek-chat", sub2api.SupportLevelDirect, true, true),
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildModelPool() error = %v", err)
|
|
}
|
|
if len(pool.Routes) != 1 {
|
|
t.Fatalf("len(pool.Routes) = %d, want 1", len(pool.Routes))
|
|
}
|
|
if pool.Routes[0].AdvertisedModel != "deepseek-v3" {
|
|
t.Fatalf("AdvertisedModel = %q, want deepseek-v3", pool.Routes[0].AdvertisedModel)
|
|
}
|
|
if pool.Routes[0].CallableModel != "deepseek-chat" {
|
|
t.Fatalf("CallableModel = %q, want deepseek-chat", pool.Routes[0].CallableModel)
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolAllowsChatOnlyRouteWhenExplicitlyEnabled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pool, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "kimi-k2.6",
|
|
AllowPluginAdapterCandidates: true,
|
|
Candidates: []ModelPoolCandidate{
|
|
{
|
|
RouteID: "route-kimi",
|
|
Provider: pack.ProviderManifest{ProviderID: "kimi-a7m", DisplayName: "Kimi", BaseURL: "https://kimi.a7m.com.cn/v1"},
|
|
Priority: 10,
|
|
Inventory: capabilityInventoryWithSupport("kimi-k2.6", "kimi-k2.6", sub2api.SupportLevelWithPluginAdapter, true, false),
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildModelPool() error = %v", err)
|
|
}
|
|
if len(pool.Routes) != 1 {
|
|
t.Fatalf("len(pool.Routes) = %d, want 1", len(pool.Routes))
|
|
}
|
|
if pool.Routes[0].SupportLevel != sub2api.SupportLevelWithPluginAdapter {
|
|
t.Fatalf("SupportLevel = %q, want %q", pool.Routes[0].SupportLevel, sub2api.SupportLevelWithPluginAdapter)
|
|
}
|
|
if pool.Routes[0].SupportsResponses {
|
|
t.Fatal("SupportsResponses = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolReturnsErrorWhenNoEligibleRoutesRemain(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "kimi-k2.6",
|
|
Candidates: []ModelPoolCandidate{{
|
|
RouteID: "route-kimi",
|
|
Provider: pack.ProviderManifest{ProviderID: "kimi-a7m", DisplayName: "Kimi", BaseURL: "https://kimi.a7m.com.cn/v1"},
|
|
Priority: 10,
|
|
Inventory: capabilityInventoryWithSupport("kimi-k2.6", "kimi-k2.6", sub2api.SupportLevelWithPluginAdapter, true, false),
|
|
}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "no eligible routes") {
|
|
t.Fatalf("BuildModelPool() error = %v, want no eligible routes", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolFiltersHostNotReadyRoute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "deepseek-chat",
|
|
Candidates: []ModelPoolCandidate{{
|
|
RouteID: "route-host-not-ready",
|
|
Provider: sampleProviderManifest(),
|
|
Priority: 10,
|
|
Inventory: capabilityInventoryWithHostReady("deepseek-chat", "deepseek-chat", sub2api.SupportLevelDirect, true, true, false),
|
|
}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "no eligible routes") {
|
|
t.Fatalf("BuildModelPool() error = %v, want no eligible routes after host_ready filter", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolFiltersUnschedulableRoute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
unschedulable := false
|
|
_, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "deepseek-chat",
|
|
Candidates: []ModelPoolCandidate{{
|
|
RouteID: "route-unschedulable",
|
|
Provider: sampleProviderManifest(),
|
|
Priority: 10,
|
|
Schedulable: &unschedulable,
|
|
Inventory: capabilityInventoryWithSupport("deepseek-chat", "deepseek-chat", sub2api.SupportLevelDirect, true, true),
|
|
}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "no eligible routes") {
|
|
t.Fatalf("BuildModelPool() error = %v, want no eligible routes after schedulable filter", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildModelPoolPreservesSupportedModelsForRoute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pool, err := BuildModelPool(ModelPoolBuildRequest{
|
|
PublicModel: "deepseek-chat",
|
|
Candidates: []ModelPoolCandidate{{
|
|
RouteID: "route-primary",
|
|
Provider: sampleProviderManifest(),
|
|
Priority: 10,
|
|
Inventory: capabilityInventoryWithMultiModels([]sub2api.ModelCapabilitySummary{
|
|
{
|
|
RawModelID: "deepseek-chat",
|
|
CanonicalModelFamily: "deepseek-chat",
|
|
SmokeChatOK: true,
|
|
SupportLevel: sub2api.SupportLevelDirect,
|
|
},
|
|
{
|
|
RawModelID: "deepseek-reasoner",
|
|
CanonicalModelFamily: "deepseek-reasoner",
|
|
SmokeChatOK: true,
|
|
SupportLevel: sub2api.SupportLevelDirect,
|
|
},
|
|
}),
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildModelPool() error = %v", err)
|
|
}
|
|
if len(pool.Routes) != 1 {
|
|
t.Fatalf("len(pool.Routes) = %d, want 1", len(pool.Routes))
|
|
}
|
|
if len(pool.Routes[0].SupportedModels) != 2 {
|
|
t.Fatalf("len(pool.Routes[0].SupportedModels) = %d, want 2", len(pool.Routes[0].SupportedModels))
|
|
}
|
|
if pool.Routes[0].SupportedModels[0] != "deepseek-chat" || pool.Routes[0].SupportedModels[1] != "deepseek-reasoner" {
|
|
t.Fatalf("SupportedModels = %#v, want [deepseek-chat deepseek-reasoner]", pool.Routes[0].SupportedModels)
|
|
}
|
|
}
|
|
|
|
func capabilityInventoryWithSupport(rawModel string, canonical string, supportLevel string, chatOK bool, responsesOK bool) sub2api.CapabilityInventory {
|
|
return capabilityInventoryWithHostReady(rawModel, canonical, supportLevel, chatOK, responsesOK, true)
|
|
}
|
|
|
|
func capabilityInventoryWithHostReady(rawModel string, canonical string, supportLevel string, chatOK bool, responsesOK bool, hostReady bool) sub2api.CapabilityInventory {
|
|
return capabilityInventoryWithMultiModelsHostReady([]sub2api.ModelCapabilitySummary{{
|
|
RawModelID: rawModel,
|
|
CanonicalModelFamily: canonical,
|
|
SmokeChatOK: chatOK,
|
|
SupportLevel: supportLevel,
|
|
KnownAdvisories: func() []string {
|
|
if responsesOK {
|
|
return nil
|
|
}
|
|
return []string{"responses_unsupported_but_chat_ok"}
|
|
}(),
|
|
}}, hostReady)
|
|
}
|
|
|
|
func capabilityInventoryWithMultiModels(models []sub2api.ModelCapabilitySummary) sub2api.CapabilityInventory {
|
|
return capabilityInventoryWithMultiModelsHostReady(models, true)
|
|
}
|
|
|
|
func capabilityInventoryWithMultiModelsHostReady(models []sub2api.ModelCapabilitySummary, hostReady bool) sub2api.CapabilityInventory {
|
|
return sub2api.CapabilityInventory{
|
|
HostReady: hostReady,
|
|
Host: sub2api.HostCapabilities{Groups: true, Channels: true, Accounts: true, AccountTest: true},
|
|
Models: models,
|
|
}
|
|
}
|