package pack import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestPublishProviderManifestWritesProviderBumpsPackAndCommits(t *testing.T) { t.Parallel() if _, err := exec.LookPath("git"); err != nil { t.Skip("git is required for publish test") } repoRoot := t.TempDir() packDir := filepath.Join(repoRoot, "packs", "openai-cn-pack") createPackFixtureAt(t, packDir, map[string]string{ "pack.json": `{ "pack_id": "openai-cn-pack", "version": "1.1.4", "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/minimax-53hk.json": `{ "provider_id": "minimax-53hk", "display_name": "MiniMax 53hk 中转兼容", "base_url": "https://api.53hk.cn/v1", "platform": "openai", "account_type": "apikey", "default_models": ["MiniMax-M2.7-highspeed"], "smoke_test_model": "MiniMax-M2.7-highspeed", "group_template": {"name": "g", "rate_multiplier": 1.0}, "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} }`, }) initGitRepo(t, repoRoot) result, err := PublishProviderManifest(context.Background(), PublishProviderManifestRequest{ RepoRoot: repoRoot, PackID: "openai-cn-pack", Manifest: ProviderManifest{ ProviderID: "openai-zhongzhuan", DisplayName: "OpenAI 中转", BaseURL: "https://api.example.com/v1", Platform: "openai", SmokeTestModel: "gpt-5.4", DefaultModels: []string{"gpt-5.4", "gpt-5.4-mini"}, }, CommitMessage: "feat(pack): publish openai-zhongzhuan", }) if err != nil { t.Fatalf("PublishProviderManifest() error = %v", err) } if result.PublishMode != "created" { t.Fatalf("PublishMode = %q, want created", result.PublishMode) } if result.PackVersionBefore != "1.1.4" || result.PackVersionAfter != "1.1.5" { t.Fatalf("pack versions = %q -> %q, want 1.1.4 -> 1.1.5", result.PackVersionBefore, result.PackVersionAfter) } if strings.TrimSpace(result.CommitSHA) == "" { t.Fatal("CommitSHA is empty") } publishedPack, err := LoadDir(packDir) if err != nil { t.Fatalf("LoadDir() after publish error = %v", err) } if publishedPack.Manifest.Version != "1.1.5" { t.Fatalf("published pack version = %q, want 1.1.5", publishedPack.Manifest.Version) } body, err := readFileString(filepath.Join(packDir, "providers", "openai-zhongzhuan.json")) if err != nil { t.Fatalf("readFileString() provider error = %v", err) } var provider ProviderManifest if err := json.Unmarshal([]byte(body), &provider); err != nil { t.Fatalf("json.Unmarshal() provider error = %v", err) } if provider.AccountType != "apikey" { t.Fatalf("AccountType = %q, want apikey", provider.AccountType) } if provider.ChannelTemplate.ModelMapping["gpt-5.4-mini"] != "gpt-5.4-mini" { t.Fatalf("ChannelTemplate.ModelMapping = %+v, want identity mapping for gpt-5.4-mini", provider.ChannelTemplate.ModelMapping) } checksums, err := readFileString(filepath.Join(packDir, "checksums.txt")) if err != nil { t.Fatalf("readFileString() checksums error = %v", err) } if !strings.Contains(checksums, "providers/openai-zhongzhuan.json") { t.Fatalf("checksums.txt = %q, want providers/openai-zhongzhuan.json entry", checksums) } } func TestPublishProviderManifestRejectsMissingRepoRoot(t *testing.T) { t.Parallel() _, err := PublishProviderManifest(context.Background(), PublishProviderManifestRequest{ PackID: "openai-cn-pack", Manifest: ProviderManifest{ ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai", SmokeTestModel: "deepseek-chat", DefaultModels: []string{"deepseek-chat"}, }, }) if err == nil || !strings.Contains(err.Error(), "repo root") { t.Fatalf("PublishProviderManifest() error = %v, want repo root detail", err) } } func createPackFixtureAt(t *testing.T, packDir string, files map[string]string) { t.Helper() var lines []string for relativePath, content := range files { absolutePath := filepath.Join(packDir, relativePath) mustWrite(t, absolutePath, content) sum := sha256Sum(content) lines = append(lines, sum+" "+relativePath) } mustWrite(t, filepath.Join(packDir, "checksums.txt"), strings.Join(lines, "\n")+"\n") } func sha256Sum(content string) string { sum := sha256.Sum256([]byte(content)) return hex.EncodeToString(sum[:]) } func initGitRepo(t *testing.T, repoRoot string) { t.Helper() runGitTest(t, repoRoot, "init") runGitTest(t, repoRoot, "config", "user.name", "Test User") runGitTest(t, repoRoot, "config", "user.email", "test@example.com") runGitTest(t, repoRoot, "add", ".") runGitTest(t, repoRoot, "commit", "-m", "chore: seed pack fixture") } func runGitTest(t *testing.T, repoRoot string, args ...string) { t.Helper() cmd := exec.Command("git", append([]string{"-C", repoRoot}, args...)...) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v error = %v: %s", args, err, strings.TrimSpace(string(output))) } } func readFileString(path string) (string, error) { body, err := os.ReadFile(path) if err != nil { return "", err } return string(body), nil }