Files
sub2api-cn-relay-manager/internal/pack/loader_test.go

156 lines
8.7 KiB
Go

package pack
import (
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
)
func TestLoadDirParsesAndValidatesPack(t *testing.T) {
packDir := createPackFixture(t, map[string]string{
"pack.json": `{
"pack_id": "openai-cn-pack",
"version": "1.0.0",
"vendor": "YourTeam",
"target_host": "sub2api",
"min_host_version": "0.1.126",
"max_host_version": "0.2.x",
"providers_dir": "providers",
"checksum_file": "checksums.txt"
}`,
"providers/deepseek.json": `{
"provider_id": "deepseek",
"display_name": "DeepSeek OpenAI Compatible",
"base_url": "https://api.deepseek.com",
"platform": "openai",
"account_type": "apikey",
"default_models": ["deepseek-chat", "deepseek-reasoner"],
"smoke_test_model": "deepseek-chat",
"group_template": {"name": "DeepSeek 默认分组", "rate_multiplier": 1.0},
"channel_template": {"name": "DeepSeek 默认渠道", "model_mapping": {"deepseek-chat": "deepseek-chat", "deepseek-reasoner": "deepseek-reasoner"}},
"plan_template": {"name": "DeepSeek 默认套餐", "price": 19.9, "validity_days": 30, "validity_unit": "day"},
"import": {"supports_multi_key": true, "supports_strict": true, "supports_partial": true}
}`,
})
loaded, err := LoadDir(packDir)
if err != nil {
t.Fatalf("LoadDir() error = %v", err)
}
if loaded.Manifest.PackID != "openai-cn-pack" {
t.Fatalf("PackID = %q, want %q", loaded.Manifest.PackID, "openai-cn-pack")
}
if len(loaded.Providers) != 1 {
t.Fatalf("len(Providers) = %d, want 1", len(loaded.Providers))
}
if loaded.Providers[0].ProviderID != "deepseek" {
t.Fatalf("ProviderID = %q, want %q", loaded.Providers[0].ProviderID, "deepseek")
}
}
func TestLoadDirRejectsChecksumMismatch(t *testing.T) {
packDir := t.TempDir()
mustWrite(t, filepath.Join(packDir, "pack.json"), `{"pack_id":"openai-cn-pack","version":"1.0.0","vendor":"x","target_host":"sub2api","min_host_version":"0.1.126","max_host_version":"0.2.x","providers_dir":"providers","checksum_file":"checksums.txt"}`)
mustWrite(t, filepath.Join(packDir, "providers", "deepseek.json"), `{"provider_id":"deepseek","display_name":"DeepSeek","base_url":"https://api.deepseek.com","platform":"openai","account_type":"api","default_models":["deepseek-chat"],"smoke_test_model":"deepseek-chat","group_template":{"name":"g","rate_multiplier":1},"channel_template":{"name":"c","model_mapping":{"deepseek-chat":"deepseek-chat"}},"plan_template":{"name":"p","price":1,"validity_days":30,"validity_unit":"day"},"import":{"supports_multi_key":true,"supports_strict":true,"supports_partial":true}}`)
mustWrite(t, filepath.Join(packDir, "checksums.txt"), "deadbeef pack.json\ndeadbeef providers/deepseek.json\n")
_, err := LoadDir(packDir)
if err == nil {
t.Fatal("LoadDir() error = nil, want checksum mismatch")
}
if !strings.Contains(err.Error(), "checksum mismatch") {
t.Fatalf("LoadDir() error = %v, want checksum mismatch", err)
}
}
func TestLoadDirRejectsInvalidProviderSchema(t *testing.T) {
packDir := createPackFixture(t, map[string]string{
"pack.json": `{"pack_id":"openai-cn-pack","version":"1.0.0","vendor":"x","target_host":"sub2api","min_host_version":"0.1.126","max_host_version":"0.2.x","providers_dir":"providers","checksum_file":"checksums.txt"}`,
"providers/deepseek.json": `{"provider_id":"deepseek","display_name":"DeepSeek","base_url":"http://insecure.example.com","platform":"openai","account_type":"api","default_models":["deepseek-chat"],"smoke_test_model":"missing-model","group_template":{"name":"g","rate_multiplier":1},"channel_template":{"name":"c","model_mapping":{"deepseek-chat":"deepseek-chat"}},"plan_template":{"name":"p","price":1,"validity_days":30,"validity_unit":"day"},"import":{"supports_multi_key":true,"supports_strict":true,"supports_partial":true}}`,
})
_, err := LoadDir(packDir)
if err == nil {
t.Fatal("LoadDir() error = nil, want schema validation failure")
}
if !strings.Contains(err.Error(), "https") && !strings.Contains(err.Error(), "smoke_test_model") {
t.Fatalf("LoadDir() error = %v, want schema validation detail", err)
}
}
func TestLoadDirAllowsInsecureProviderBaseURLWhenExplicitlyEnabled(t *testing.T) {
t.Setenv(envAllowInsecureProviderBaseURL, "1")
packDir := createPackFixture(t, map[string]string{
"pack.json": `{"pack_id":"openai-cn-pack","version":"1.0.0","vendor":"x","target_host":"sub2api","min_host_version":"0.1.126","max_host_version":"0.2.x","providers_dir":"providers","checksum_file":"checksums.txt"}`,
"providers/deepseek.json": `{"provider_id":"deepseek","display_name":"DeepSeek","base_url":"http://insecure.example.com","platform":"openai","account_type":"apikey","default_models":["deepseek-v4-pro"],"smoke_test_model":"deepseek-v4-pro","group_template":{"name":"g","rate_multiplier":1},"channel_template":{"name":"c","model_mapping":{"deepseek-v4-pro":"deepseek-v4-pro"}},"plan_template":{"name":"p","price":1,"validity_days":30,"validity_unit":"day"},"import":{"supports_multi_key":true,"supports_strict":true,"supports_partial":true}}`,
})
loaded, err := LoadDir(packDir)
if err != nil {
t.Fatalf("LoadDir() error = %v, want insecure http provider accepted when explicitly enabled", err)
}
if len(loaded.Providers) != 1 || loaded.Providers[0].BaseURL != "http://insecure.example.com" {
t.Fatalf("LoadDir() providers = %+v, want insecure provider retained", loaded.Providers)
}
}
func TestLoadDirRejectsSmokeTestModelMissingFromChannelMapping(t *testing.T) {
packDir := createPackFixture(t, map[string]string{
"pack.json": `{"pack_id":"openai-cn-pack","version":"1.0.0","vendor":"x","target_host":"sub2api","min_host_version":"0.1.126","max_host_version":"0.2.x","providers_dir":"providers","checksum_file":"checksums.txt"}`,
"providers/deepseek.json": `{"provider_id":"deepseek","display_name":"DeepSeek","base_url":"https://api.deepseek.com","platform":"openai","account_type":"apikey","default_models":["deepseek-v4-pro","deepseek-v4-flash"],"smoke_test_model":"deepseek-v4-pro","group_template":{"name":"g","rate_multiplier":1},"channel_template":{"name":"c","model_mapping":{"deepseek-chat":"deepseek-chat","deepseek-reasoner":"deepseek-reasoner"}},"plan_template":{"name":"p","price":1,"validity_days":30,"validity_unit":"day"},"import":{"supports_multi_key":true,"supports_strict":true,"supports_partial":true}}`,
})
_, err := LoadDir(packDir)
if err == nil {
t.Fatal("LoadDir() error = nil, want smoke_test_model channel mapping validation failure")
}
if !strings.Contains(err.Error(), "channel_template.model_mapping") || !strings.Contains(err.Error(), "smoke_test_model") {
t.Fatalf("LoadDir() error = %v, want smoke_test_model channel mapping detail", err)
}
}
func TestLoadDirRejectsDefaultModelsMissingFromChannelMapping(t *testing.T) {
packDir := createPackFixture(t, map[string]string{
"pack.json": `{"pack_id":"openai-cn-pack","version":"1.0.0","vendor":"x","target_host":"sub2api","min_host_version":"0.1.126","max_host_version":"0.2.x","providers_dir":"providers","checksum_file":"checksums.txt"}`,
"providers/minimax.json": `{"provider_id":"minimax","display_name":"MiniMax","base_url":"https://api.minimax.example.com","platform":"openai","account_type":"apikey","default_models":["MiniMax-M2.5-highspeed","MiniMax-M2.7-highspeed"],"smoke_test_model":"MiniMax-M2.7-highspeed","group_template":{"name":"g","rate_multiplier":1},"channel_template":{"name":"c","model_mapping":{"MiniMax-M2.7-highspeed":"MiniMax-M2.7-highspeed"}},"plan_template":{"name":"p","price":1,"validity_days":30,"validity_unit":"day"},"import":{"supports_multi_key":true,"supports_strict":true,"supports_partial":true}}`,
})
_, err := LoadDir(packDir)
if err == nil {
t.Fatal("LoadDir() error = nil, want default_models channel mapping validation failure")
}
if !strings.Contains(err.Error(), "default_models") || !strings.Contains(err.Error(), "channel_template.model_mapping") {
t.Fatalf("LoadDir() error = %v, want default_models channel mapping detail", err)
}
}
func createPackFixture(t *testing.T, files map[string]string) string {
t.Helper()
packDir := t.TempDir()
var lines []string
for relativePath, content := range files {
absolutePath := filepath.Join(packDir, relativePath)
mustWrite(t, absolutePath, content)
sum := sha256.Sum256([]byte(content))
lines = append(lines, hex.EncodeToString(sum[:])+" "+relativePath)
}
mustWrite(t, filepath.Join(packDir, "checksums.txt"), strings.Join(lines, "\n")+"\n")
return packDir
}
func mustWrite(t *testing.T, path string, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", path, err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}