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) } }