ccpoad / internal /model /model_test.go
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
10.5 kB
package model
import (
"encoding/json"
"strconv"
"testing"
"time"
)
// ==================== JSONTime 序列化测试 ====================
func TestJSONTime_MarshalJSON(t *testing.T) {
testTime := time.Date(2025, 10, 4, 10, 30, 45, 0, time.UTC)
testTimestamp := testTime.Unix()
tests := []struct {
name string
time time.Time
expected string
}{
{
name: "标准时间序列化为Unix时间戳",
time: testTime,
expected: strconv.FormatInt(testTimestamp, 10),
},
{
name: "带时区时间序列化为Unix时间戳",
time: time.Date(2025, 10, 4, 18, 30, 45, 0, time.FixedZone("CST", 8*3600)),
expected: strconv.FormatInt(testTimestamp, 10), // CST 18:30 = UTC 10:30
},
{
name: "零时间序列化为0",
time: time.Time{},
expected: "0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jt := JSONTime{Time: tt.time}
data, err := json.Marshal(jt)
if err != nil {
t.Fatalf("序列化失败: %v", err)
}
if string(data) != tt.expected {
t.Errorf("序列化结果不匹配:\n期望: %s\n实际: %s", tt.expected, string(data))
}
})
}
}
func TestJSONTime_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
input string
expected time.Time
wantErr bool
}{
{
name: "Unix时间戳反序列化",
input: `1759575045`,
expected: time.Unix(1759575045, 0),
wantErr: false,
},
{
name: "Unix时间戳字符串反序列化",
input: `"1759575045"`, // 带引号的字符串(兼容性测试)
expected: time.Unix(1759575045, 0),
wantErr: true, // 新实现不支持字符串格式,应该报错
},
{
name: "null值处理",
input: `null`,
expected: time.Time{},
wantErr: false,
},
{
name: "零值处理",
input: `0`,
expected: time.Time{},
wantErr: false,
},
{
name: "无效格式返回错误",
input: `"invalid-time"`,
expected: time.Time{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var jt JSONTime
err := json.Unmarshal([]byte(tt.input), &jt)
if tt.wantErr {
if err == nil {
t.Errorf("期望错误但成功反序列化")
}
return
}
if err != nil {
t.Fatalf("反序列化失败: %v", err)
}
if !jt.Equal(tt.expected) {
t.Errorf("时间不匹配:\n期望: %v\n实际: %v", tt.expected, jt.Time)
}
})
}
}
// ==================== Config 序列化完整性测试 ====================
func TestConfig_JSONSerialization(t *testing.T) {
now := time.Now()
config := &Config{
ID: 1,
Name: "test-channel",
ChannelType: "gemini",
URL: "https://api.example.com",
Priority: 10,
ModelEntries: []ModelEntry{
{Model: "model-1", RedirectModel: ""},
{Model: "model-2", RedirectModel: "model-2-new"},
},
Enabled: true,
CreatedAt: JSONTime{Time: now},
UpdatedAt: JSONTime{Time: now},
}
// 序列化
data, err := json.Marshal(config)
if err != nil {
t.Fatalf("序列化失败: %v", err)
}
// 反序列化
var restored Config
if err := json.Unmarshal(data, &restored); err != nil {
t.Fatalf("反序列化失败: %v", err)
}
// 验证关键字段
if restored.ChannelType != "gemini" {
t.Errorf("channel_type不匹配: 期望 gemini, 实际 %s", restored.ChannelType)
}
if restored.Name != "test-channel" {
t.Errorf("name不匹配: 期望 test-channel, 实际 %s", restored.Name)
}
if len(restored.ModelEntries) != 2 {
t.Errorf("model_entries数量不匹配: 期望 2, 实际 %d", len(restored.ModelEntries))
}
// 验证 GetModels() 返回正确的模型列表
models := restored.GetModels()
if len(models) != 2 {
t.Errorf("GetModels()数量不匹配: 期望 2, 实际 %d", len(models))
}
// 验证 GetRedirectModel() 返回正确的重定向
if redirect, ok := restored.GetRedirectModel("model-2"); !ok || redirect != "model-2-new" {
t.Errorf("GetRedirectModel()不匹配: 期望 (model-2-new, true), 实际 (%s, %v)", redirect, ok)
}
// 时间比较:允许1秒误差(JSON序列化精度损失)
if !restored.CreatedAt.Time.Truncate(time.Second).Equal(now.Truncate(time.Second)) {
t.Errorf("created_at时间不匹配:\n期望: %v\n实际: %v", now, restored.CreatedAt.Time)
}
}
// ==================== GetChannelType 默认值测试 ====================
func TestConfig_GetChannelType(t *testing.T) {
tests := []struct {
name string
channelType string
expected string
}{
{
name: "非空值原样返回",
channelType: "gemini",
expected: "gemini",
},
{
name: "空字符串返回默认值",
channelType: "",
expected: "anthropic",
},
{
name: "codex值正确返回",
channelType: "codex",
expected: "codex",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &Config{ChannelType: tt.channelType}
result := config.GetChannelType()
if result != tt.expected {
t.Errorf("GetChannelType()结果不匹配:\n期望: %s\n实际: %s", tt.expected, result)
}
})
}
}
// ==================== 模糊匹配测试 ====================
func TestConfig_FuzzyMatchModel(t *testing.T) {
tests := []struct {
name string
models []ModelEntry
query string
expectMatch bool
expectedModel string
}{
{
name: "子串匹配-sonnet匹配claude-sonnet",
models: []ModelEntry{
{Model: "claude-sonnet-4-5-20250929"},
{Model: "claude-3-sonnet-20240229"},
},
query: "sonnet",
expectMatch: true,
expectedModel: "claude-sonnet-4-5-20250929", // 日期更新
},
{
name: "子串匹配-gpt匹配多个版本",
models: []ModelEntry{
{Model: "gpt-4"},
{Model: "gpt-5.2"},
{Model: "gpt-4o"},
},
query: "gpt",
expectMatch: true,
expectedModel: "gpt-5.2", // 版本号更大
},
{
name: "精确匹配优先于模糊匹配",
models: []ModelEntry{
{Model: "sonnet"},
{Model: "claude-sonnet-4-5"},
},
query: "sonnet",
expectMatch: true,
expectedModel: "claude-sonnet-4-5", // 版本号更大(4,5 vs 无版本号)
},
{
name: "无匹配返回false",
models: []ModelEntry{
{Model: "claude-opus"},
{Model: "gemini-pro"},
},
query: "gpt",
expectMatch: false,
},
{
name: "空query返回false",
models: []ModelEntry{
{Model: "claude-sonnet"},
},
query: "",
expectMatch: false,
},
{
name: "单个匹配直接返回",
models: []ModelEntry{
{Model: "gemini-3-flash-preview"},
{Model: "gpt-4"},
},
query: "flash",
expectMatch: true,
expectedModel: "gemini-3-flash-preview",
},
{
name: "大小写不敏感",
models: []ModelEntry{
{Model: "Claude-Sonnet-4-5"},
},
query: "SONNET",
expectMatch: true,
expectedModel: "Claude-Sonnet-4-5",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &Config{ModelEntries: tt.models}
matched, ok := cfg.FuzzyMatchModel(tt.query)
if ok != tt.expectMatch {
t.Errorf("FuzzyMatchModel() 匹配结果不符: 期望 %v, 实际 %v", tt.expectMatch, ok)
return
}
if tt.expectMatch && matched != tt.expectedModel {
t.Errorf("FuzzyMatchModel() 模型不符: 期望 %s, 实际 %s", tt.expectedModel, matched)
}
})
}
}
func TestCompareModelVersion(t *testing.T) {
tests := []struct {
name string
a string
b string
expected int // >0: a更新, <0: b更新, 0: 相同
}{
{
name: "日期优先-a日期更新",
a: "claude-sonnet-4-5-20250929",
b: "claude-sonnet-4-5-20241022",
expected: 1,
},
{
name: "日期优先-b日期更新",
a: "claude-3-sonnet-20240229",
b: "claude-3-sonnet-20241022",
expected: -1,
},
{
name: "版本号比较-主版本更大",
a: "gpt-5.2",
b: "gpt-4.5",
expected: 1,
},
{
name: "版本号比较-次版本更大",
a: "claude-3-5-sonnet",
b: "claude-3-sonnet",
expected: 1, // [3,5] > [3]
},
{
name: "无日期vs有日期-有日期更新",
a: "claude-sonnet-4-5",
b: "claude-sonnet-4-5-20250929",
expected: -1, // b有日期
},
{
name: "相同模型",
a: "gpt-4",
b: "gpt-4",
expected: 0,
},
{
name: "字典序兜底",
a: "model-b",
b: "model-a",
expected: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := compareModelVersion(tt.a, tt.b)
if tt.expected > 0 && result <= 0 {
t.Errorf("compareModelVersion(%s, %s) = %d, 期望 >0", tt.a, tt.b, result)
} else if tt.expected < 0 && result >= 0 {
t.Errorf("compareModelVersion(%s, %s) = %d, 期望 <0", tt.a, tt.b, result)
} else if tt.expected == 0 && result != 0 {
t.Errorf("compareModelVersion(%s, %s) = %d, 期望 0", tt.a, tt.b, result)
}
})
}
}
func TestExtractDateSuffix(t *testing.T) {
tests := []struct {
model string
expected string
}{
{"claude-sonnet-4-5-20250929", "20250929"},
{"claude-3-sonnet-20241022", "20241022"},
{"gpt-5.2", ""},
{"gemini-3-flash-preview", ""},
{"model.20250101", "20250101"}, // 支持.分隔
{"no-date", ""},
{"invalid-12345678", ""}, // 非法日期格式
}
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
result := extractDateSuffix(tt.model)
if result != tt.expected {
t.Errorf("extractDateSuffix(%s) = %s, 期望 %s", tt.model, result, tt.expected)
}
})
}
}
func TestExtractVersionNumbers(t *testing.T) {
tests := []struct {
model string
expected []int
}{
{"gpt-5.2", []int{5, 2}},
{"claude-sonnet-4-5-20250929", []int{4, 5}}, // 日期被移除
{"claude-3-5-sonnet", []int{3, 5}},
{"gemini-3-flash-preview", []int{3}},
{"model-without-version", []int{}},
}
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
result := extractVersionNumbers(tt.model)
if len(result) != len(tt.expected) {
t.Errorf("extractVersionNumbers(%s) = %v, 期望 %v", tt.model, result, tt.expected)
return
}
for i, v := range result {
if v != tt.expected[i] {
t.Errorf("extractVersionNumbers(%s) = %v, 期望 %v", tt.model, result, tt.expected)
return
}
}
})
}
}