108 lines
5.5 KiB
Go
108 lines
5.5 KiB
Go
package app
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"infinite-canvas/backend/internal/model"
|
||
)
|
||
|
||
// 模型级上游键重命名后,任务请求必须使用新键;价格档中与旧值相同的上游键属于
|
||
// “跟随模型默认”的隐式固化,保存时必须级联跟随(见 SaveAdminChannelModel)。
|
||
func upstreamRenameFixture(t *testing.T) (*Service, *model.User, *model.ModelChannel) {
|
||
t.Helper()
|
||
svc, db := newChannelModelTestService(t)
|
||
admin := &model.User{ID: "admin", Role: model.UserRoleAdmin}
|
||
channel := model.ModelChannel{ID: "channel-1", UserID: admin.ID, Scope: model.ChannelScopeSystem, Enabled: true, Name: "model-gateway", BaseURL: "https://example.com/v1", APIKey: "key", APIFormat: "openai", ModelsJSON: `[]`}
|
||
if err := db.Create(&channel).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return svc, admin, &channel
|
||
}
|
||
|
||
func TestSaveAdminChannelModelCascadesUpstreamKeyToMatchingTiers(t *testing.T) {
|
||
svc, admin, channel := upstreamRenameFixture(t)
|
||
enabled := true
|
||
first, err := svc.SaveAdminChannelModel(admin, channel.ID, "", ChannelModelRequest{
|
||
ModelKey: "deepseek-v4-flash", ProviderModelKey: "deepseek-v4-flash", DisplayName: "deepseek-v4-flash",
|
||
Capability: "text", Protocol: string(model.ChannelInterfaceChatCompletion),
|
||
CapabilityConfig: DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceChatCompletion), "deepseek-v4-flash"),
|
||
PriceTiers: []ChannelModelPriceTierRequest{
|
||
{Selector: map[string]string{}, ProviderModelKey: "deepseek-v4-flash", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled},
|
||
{Selector: map[string]string{"operation": "text_generation"}, ProviderModelKey: "deepseek-v4-flash-preview", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled},
|
||
},
|
||
Enabled: &enabled,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("initial save failed: %v", err)
|
||
}
|
||
|
||
// 管理端表单会把已存档位键原样回传(统一价格档的上游键字段不可见),重命名顶层上游键。
|
||
renamed, err := svc.SaveAdminChannelModel(admin, channel.ID, first.ID, ChannelModelRequest{
|
||
ModelKey: "deepseek-v4.1-flash", ProviderModelKey: "deepseek-v4.1-flash", DisplayName: "deepseek-v4.1-flash",
|
||
Capability: "text", Protocol: string(model.ChannelInterfaceChatCompletion),
|
||
CapabilityConfig: DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceChatCompletion), "deepseek-v4.1-flash"),
|
||
PriceTiers: []ChannelModelPriceTierRequest{
|
||
{Selector: map[string]string{}, ProviderModelKey: "deepseek-v4-flash", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled},
|
||
{Selector: map[string]string{"operation": "text_generation"}, ProviderModelKey: "deepseek-v4-flash-preview", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled},
|
||
},
|
||
Enabled: &enabled,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("rename save failed: %v", err)
|
||
}
|
||
if len(renamed.PriceTiers) != 2 {
|
||
t.Fatalf("price tiers = %#v, want 2", renamed.PriceTiers)
|
||
}
|
||
for _, tier := range renamed.PriceTiers {
|
||
if tier.ProviderModelKey == "deepseek-v4-flash" {
|
||
t.Fatalf("tier %s still carries the retired upstream key deepseek-v4-flash; it must follow the model rename", tier.ID)
|
||
}
|
||
}
|
||
// 管理员显式配置的其他上游 SKU 不能被级联覆盖。
|
||
keptDistinct := false
|
||
for _, tier := range renamed.PriceTiers {
|
||
if tier.ProviderModelKey == "deepseek-v4-flash-preview" {
|
||
keptDistinct = true
|
||
}
|
||
}
|
||
if !keptDistinct {
|
||
t.Fatalf("distinct tier upstream key must survive the rename: %#v", renamed.PriceTiers)
|
||
}
|
||
}
|
||
|
||
func TestRenamedUpstreamKeyReachesSystemChannelRequests(t *testing.T) {
|
||
svc, admin, channel := upstreamRenameFixture(t)
|
||
enabled := true
|
||
first, err := svc.SaveAdminChannelModel(admin, channel.ID, "", ChannelModelRequest{
|
||
ModelKey: "deepseek-v4-flash", ProviderModelKey: "deepseek-v4-flash", DisplayName: "deepseek-v4-flash",
|
||
Capability: "text", Protocol: string(model.ChannelInterfaceChatCompletion),
|
||
CapabilityConfig: DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceChatCompletion), "deepseek-v4-flash"),
|
||
PriceTiers: []ChannelModelPriceTierRequest{{ProviderModelKey: "deepseek-v4-flash", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled}},
|
||
Enabled: &enabled,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("initial save failed: %v", err)
|
||
}
|
||
if _, err := svc.SaveAdminChannelModel(admin, channel.ID, first.ID, ChannelModelRequest{
|
||
ModelKey: "deepseek-v4.1-flash", ProviderModelKey: "deepseek-v4.1-flash", DisplayName: "deepseek-v4.1-flash",
|
||
Capability: "text", Protocol: string(model.ChannelInterfaceChatCompletion),
|
||
CapabilityConfig: DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceChatCompletion), "deepseek-v4.1-flash"),
|
||
PriceTiers: []ChannelModelPriceTierRequest{{ProviderModelKey: "deepseek-v4-flash", BillingMode: "fixed_request", PriceConfigured: true, Enabled: &enabled}},
|
||
Enabled: &enabled,
|
||
}); err != nil {
|
||
t.Fatalf("rename save failed: %v", err)
|
||
}
|
||
|
||
// 用户在画布 Agent 选中新模型发起任务;config 形状与 cloud_agent.go 组装一致。
|
||
resolved, err := svc.resolveSystemChannelModelSelection(map[string]any{
|
||
"config": map[string]any{"channelId": channel.ID, "model": "deepseek-v4.1-flash", "channelModelKey": "deepseek-v4.1-flash"},
|
||
}, "canvas_text", "")
|
||
if err != nil {
|
||
t.Fatalf("resolveSystemChannelModelSelection() error = %v", err)
|
||
}
|
||
config := resolved["config"].(map[string]any)
|
||
if got := config["providerModelKey"]; got != "deepseek-v4.1-flash" {
|
||
t.Fatalf("providerModelKey = %v, want deepseek-v4.1-flash (user-selected model must reach the upstream request)", got)
|
||
}
|
||
}
|