156 lines
8.7 KiB
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)
|
|
}
|
|
}
|