176 lines
6.8 KiB
Go
176 lines
6.8 KiB
Go
package service
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
)
|
|
|
|
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 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)
|
|
}
|
|
}
|