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

230 lines
9.1 KiB
Go

package app
import (
"strings"
"testing"
"infinite-canvas/backend/internal/model"
)
func TestCapabilitySpecWithRoutePresetsRestoresImageSize(t *testing.T) {
product := CapabilitySpec{Version: 1, Capability: "image", Options: map[string]OptionConstraint{
"size": {Values: []any{"16:9"}},
}}
route := CapabilitySpec{
Version: 1,
Capability: "image",
Options: map[string]OptionConstraint{"size": {Values: []any{"16:9"}}},
ImageSize: &CapabilityImageSize{
Parameter: "aspect_ratio",
Presets: []CapabilityImageSizePreset{{Size: "3840x2160", Tier: "4k", Ratio: "16:9", Width: 3840, Height: 2160}},
},
}
got := capabilitySpecWithRoutePresets(product, []CapabilitySpec{route})
if got.ImageSize == nil || got.ImageSize.Parameter != "aspect_ratio" || len(got.ImageSize.Presets) != 1 || got.ImageSize.Presets[0].Tier != "4k" {
t.Fatalf("restored imageSize = %#v", got.ImageSize)
}
}
func TestMatchCapabilityRoutesTextImagesOnlyToDeclaredProfiles(t *testing.T) {
plainText := CapabilitySpec{
Version: 1,
Capability: "text",
Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 0}},
}
visionText := CapabilitySpec{
Version: 1,
Capability: "text",
Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 8}},
}
intent := ModelRequestIntent{Capability: "text", Inputs: map[string]int{"image": 2}}
if match := MatchCapability(plainText, intent); match.Matched {
t.Fatalf("plain text profile unexpectedly matched image input: %#v", match)
}
if match := MatchCapability(visionText, intent); !match.Matched {
t.Fatalf("vision text profile did not match image input: %#v", match)
}
}
func TestMatchCapabilityAcceptsWildcardOptionValues(t *testing.T) {
spec := CapabilitySpec{
Version: 1,
Capability: "image",
Options: map[string]OptionConstraint{"size": {Values: []any{"*"}}},
}
intent := ModelRequestIntent{Capability: "image", Options: map[string]any{"size": "1024x1024"}}
if match := MatchCapability(spec, intent); !match.Matched {
t.Fatalf("wildcard option did not match custom value: %#v", match)
}
}
func TestMatchCapabilityAcceptsWildcardAlongsidePresetOptionValues(t *testing.T) {
intent := ModelRequestIntent{Capability: "image", Options: map[string]any{"size": "2:1"}}
spec := CapabilitySpec{
Version: 1,
Capability: "image",
Options: map[string]OptionConstraint{"size": {Values: []any{"1:1", "9:16", "*"}}},
}
match := MatchCapability(spec, intent)
if !match.Matched {
t.Fatalf("wildcard alongside presets did not match custom value: %#v", match)
}
}
func TestMatchCapabilityTreatsVideoResolutionSuffixAsEquivalent(t *testing.T) {
spec := CapabilitySpec{
Version: 1,
Capability: "video",
Options: map[string]OptionConstraint{"vquality": {Values: []any{"720p"}}},
}
intent := ModelRequestIntent{Capability: "video", Options: map[string]any{"vquality": "720"}}
if match := MatchCapability(spec, intent); !match.Matched {
t.Fatalf("720 and 720p should match the same video resolution: %#v", match)
}
}
func TestVideoResolutionAliasesAcrossProductAndPricedRoutes(t *testing.T) {
product := CapabilitySpec{
Version: 1, Capability: "video",
Options: map[string]OptionConstraint{"vquality": {Values: []any{"768P", "2K"}}},
}
route := capabilitySpecWithPriceTiers(product, model.ChannelModel{
PriceTiers: []model.ChannelModelPriceTier{
{Resolution: "768P", Enabled: true, PriceConfigured: true},
{Resolution: "2K", Enabled: true, PriceConfigured: true},
},
})
if err := validateProductSpecWithinRoutes(product, []CapabilitySpec{route}); err != nil {
t.Fatalf("2K product rejected against priced route: %v", err)
}
for _, resolution := range []string{"2K", "2k", "1440P", "1440"} {
intent := ModelRequestIntent{Capability: "video", Options: map[string]any{
"vquality": normalizeModelRequestOption("vquality", resolution),
}}
for name, spec := range map[string]CapabilitySpec{"product": product, "route": route} {
if match := MatchCapability(spec, intent); !match.Matched {
t.Errorf("%s rejected %s: %#v", name, resolution, match)
}
}
}
unsupported := CapabilitySpec{Version: 1, Capability: "video", Options: map[string]OptionConstraint{
"vquality": {Values: []any{"4K"}},
}}
if err := validateProductSpecWithinRoutes(unsupported, []CapabilitySpec{route}); err == nil {
t.Fatal("2K route accepted unsupported 4K product")
}
if capabilityOptionValuesEqual("quality", "2K", "1440p") {
t.Fatal("video aliases must not affect image quality")
}
}
func TestValidateProductSpecWithinRoutesRejectsUnsupportedCapabilityValue(t *testing.T) {
routes := []CapabilitySpec{
{
Version: 1,
Capability: "video",
Operations: []string{"text_to_video"},
Options: map[string]OptionConstraint{
"vquality": {Values: []any{"720p"}},
},
},
{
Version: 1,
Capability: "video",
Operations: []string{"image_to_video"},
Options: map[string]OptionConstraint{
"vquality": {Values: []any{"1080p"}},
},
},
}
product := CapabilitySpec{
Version: 1,
Capability: "video",
Operations: []string{"text_to_video", "image_to_video"},
Options: map[string]OptionConstraint{
"vquality": {Values: []any{"720p", "4k"}},
},
}
err := validateProductSpecWithinRoutes(product, routes)
if err == nil || !strings.Contains(err.Error(), "vquality") {
t.Fatalf("validateProductSpecWithinRoutes() error = %v", err)
}
}
func TestValidateProductSpecWithinRoutesPreservesInputRangeCoverage(t *testing.T) {
routes := []CapabilitySpec{
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 4}}},
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 5, Max: 9}}},
}
covered := CapabilitySpec{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 9}}}
if err := validateProductSpecWithinRoutes(covered, routes); err != nil {
t.Fatalf("covered input range rejected: %v", err)
}
routes[1].Inputs["image"] = InputConstraint{Min: 6, Max: 9}
if err := validateProductSpecWithinRoutes(covered, routes); err == nil {
t.Fatal("input range with an uncovered count was accepted")
}
}
func TestLogicalModelConfigurationErrorAfterRouteCoverageShrinks(t *testing.T) {
product := CapabilitySpec{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 9}}}
complete := []CapabilitySpec{
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 4}}},
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 5, Max: 9}}},
}
if message := logicalModelConfigurationError(product, complete); message != "" {
t.Fatalf("complete route coverage reported an error: %s", message)
}
if message := logicalModelConfigurationError(product, complete[:1]); !strings.Contains(message, "无法完整覆盖") {
t.Fatalf("shrunk route coverage error = %q", message)
}
}
func TestLogicalModelAvailabilityErrorRequiresSettlementReadyCoverage(t *testing.T) {
product := CapabilitySpec{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 9}}}
structural := []CapabilitySpec{
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 0, Max: 4}}},
{Version: 1, Capability: "image", Inputs: map[string]InputConstraint{"image": {Min: 5, Max: 9}}},
}
if message := logicalModelAvailabilityError("channel", product, structural, nil); !strings.Contains(message, "可结算价格") {
t.Fatalf("missing settlement route error = %q", message)
}
if message := logicalModelAvailabilityError("channel", product, structural, structural[:1]); !strings.Contains(message, "部分创作端能力") {
t.Fatalf("partial settlement coverage error = %q", message)
}
if message := logicalModelAvailabilityError("channel", product, structural, structural); message != "" {
t.Fatalf("complete settlement coverage error = %q", message)
}
if message := logicalModelAvailabilityError("unified", product, structural, nil); message != "" {
t.Fatalf("unified pricing unexpectedly required channel prices: %q", message)
}
}
func TestSupportsLogicalModelTokenBillingForArkVideoRoutes(t *testing.T) {
if !supportsLogicalModelTokenBilling("text", nil) {
t.Fatal("text logical models should support Token billing")
}
if !supportsLogicalModelTokenBilling("video", []model.ChannelInterfaceType{model.ChannelInterfaceVolcengineArkVideo}) {
t.Fatal("Ark video-only routes should support Token billing")
}
if supportsLogicalModelTokenBilling("video", nil) {
t.Fatal("video logical models without an enabled route must not support Token billing")
}
if supportsLogicalModelTokenBilling("video", []model.ChannelInterfaceType{model.ChannelInterfaceVolcengineArkVideo, model.ChannelInterfaceNewAPIVideo}) {
t.Fatal("mixed video protocols must not support Token billing")
}
}
func TestChannelModelCapabilitySpecRequiresExplicitImageCapability(t *testing.T) {
channelModel := model.ChannelModel{Capability: "image", CapabilityConfigJSON: ""}
_, err := channelModelCapabilitySpec(channelModel)
if err == nil || !strings.Contains(err.Error(), "渠道图片模型尚未配置能力参数") {
t.Fatalf("channelModelCapabilitySpec() error = %v", err)
}
}