Files
ttstd-infinite-canvas/backend/internal/app/channel_model_upstream_rename_test.go

108 lines
5.5 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.
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)
}
}