package pack import ( "archive/zip" "os" "path/filepath" "strings" "testing" ) func TestValidateManifestRequiredFields(t *testing.T) { tests := []struct { name string manifest Manifest wantErr string }{ { name: "empty pack id", manifest: Manifest{ Version: "1.0.0", TargetHost: "sub2api", ProvidersDir: "providers", ChecksumFile: "checksums.txt", }, wantErr: "pack_id is required", }, { name: "empty version", manifest: Manifest{ PackID: "openai-cn-pack", TargetHost: "sub2api", ProvidersDir: "providers", ChecksumFile: "checksums.txt", }, wantErr: "version is required", }, { name: "empty target host", manifest: Manifest{ PackID: "openai-cn-pack", Version: "1.0.0", ProvidersDir: "providers", ChecksumFile: "checksums.txt", }, wantErr: "target_host is required", }, { name: "empty providers dir", manifest: Manifest{ PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", ChecksumFile: "checksums.txt", }, wantErr: "providers_dir is required", }, { name: "empty checksum file", manifest: Manifest{ PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", ProvidersDir: "providers", }, wantErr: "checksum_file is required", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateManifest(tt.manifest) if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("validateManifest() error = %v, want substring %q", err, tt.wantErr) } }) } } func TestValidateProvidersRejectsInvalidProviderFields(t *testing.T) { tests := []struct { name string providers []ProviderManifest wantErr string }{ { name: "empty provider id", providers: []ProviderManifest{{ DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai", AccountType: "apikey", DefaultModels: []string{"deepseek-chat"}, SmokeTestModel: "deepseek-chat", GroupTemplate: GroupTemplate{Name: "g"}, ChannelTemplate: ChannelTemplate{ Name: "c", ModelMapping: map[string]string{"deepseek-chat": "deepseek-chat"}, }, PlanTemplate: PlanTemplate{Name: "p", ValidityDays: 30}, }}, wantErr: "provider_id is required", }, { name: "empty base url", providers: []ProviderManifest{{ ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "", Platform: "openai", AccountType: "apikey", DefaultModels: []string{"deepseek-chat"}, SmokeTestModel: "deepseek-chat", GroupTemplate: GroupTemplate{Name: "g"}, ChannelTemplate: ChannelTemplate{ Name: "c", ModelMapping: map[string]string{"deepseek-chat": "deepseek-chat"}, }, PlanTemplate: PlanTemplate{Name: "p", ValidityDays: 30}, }}, wantErr: "base_url must use https", }, { name: "missing display name", providers: []ProviderManifest{{ ProviderID: "deepseek", DisplayName: "", BaseURL: "https://api.deepseek.com", Platform: "openai", AccountType: "apikey", DefaultModels: []string{"deepseek-chat"}, SmokeTestModel: "deepseek-chat", GroupTemplate: GroupTemplate{Name: "g"}, ChannelTemplate: ChannelTemplate{ Name: "c", ModelMapping: map[string]string{"deepseek-chat": "deepseek-chat"}, }, PlanTemplate: PlanTemplate{Name: "p", ValidityDays: 30}, }}, wantErr: "display_name is required", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateProviders(t.TempDir(), tt.providers) if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("validateProviders() error = %v, want substring %q", err, tt.wantErr) } }) } } func TestExtractZipToTempRejectsEmptyArchive(t *testing.T) { archivePath := filepath.Join(t.TempDir(), "empty.zip") file, err := os.Create(archivePath) if err != nil { t.Fatalf("os.Create() error = %v", err) } writer := zip.NewWriter(file) if err := writer.Close(); err != nil { t.Fatalf("writer.Close() error = %v", err) } if err := file.Close(); err != nil { t.Fatalf("file.Close() error = %v", err) } _, cleanup, err := extractZipToTemp(archivePath) if cleanup != nil { cleanup() } if err == nil || !strings.Contains(err.Error(), "pack archive is empty") { t.Fatalf("extractZipToTemp() error = %v, want empty archive error", err) } } func TestExtractZipToTempRejectsPathTraversal(t *testing.T) { archivePath := filepath.Join(t.TempDir(), "traversal.zip") file, err := os.Create(archivePath) if err != nil { t.Fatalf("os.Create() error = %v", err) } writer := zip.NewWriter(file) entry, err := writer.Create("../../../evil.txt") if err != nil { t.Fatalf("writer.Create() error = %v", err) } if _, err := entry.Write([]byte("evil")); err != nil { t.Fatalf("entry.Write() error = %v", err) } if err := writer.Close(); err != nil { t.Fatalf("writer.Close() error = %v", err) } if err := file.Close(); err != nil { t.Fatalf("file.Close() error = %v", err) } _, cleanup, err := extractZipToTemp(archivePath) if cleanup != nil { cleanup() } if err == nil || !strings.Contains(err.Error(), "invalid path") { t.Fatalf("extractZipToTemp() error = %v, want invalid path error", err) } } func TestMatchesMaxConstraintCases(t *testing.T) { tests := []struct { name string hostVersion string maxVersion string want bool }{ {name: "exact version match", hostVersion: "1.2.3", maxVersion: "1.2.3", want: true}, {name: "wildcard x accepts same minor", hostVersion: "0.2.9", maxVersion: "0.2.x", want: true}, {name: "non matching version", hostVersion: "1.2.4", maxVersion: "1.2.3", want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := matchesMaxConstraint(tt.hostVersion, tt.maxVersion) if err != nil { t.Fatalf("matchesMaxConstraint() error = %v", err) } if got != tt.want { t.Fatalf("matchesMaxConstraint() = %v, want %v", got, tt.want) } }) } } func TestMatchesMaxConstraintRejectsWildcardStar(t *testing.T) { _, err := matchesMaxConstraint("1.2.3", "1.2.*") if err == nil || !strings.Contains(err.Error(), `parse version segment "*"`) { t.Fatalf("matchesMaxConstraint() error = %v, want parse failure for wildcard star", err) } } func TestLoadPathRejectsEmptyAndMissingPath(t *testing.T) { tests := []struct { name string path string wantErr string }{ {name: "empty path", path: " ", wantErr: "pack path is required"}, {name: "missing path", path: filepath.Join(t.TempDir(), "missing-pack"), wantErr: "stat pack path"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := LoadPath(tt.path) if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("LoadPath() error = %v, want substring %q", err, tt.wantErr) } }) } } func TestLoadArchiveRejectsNonZipFile(t *testing.T) { filePath := filepath.Join(t.TempDir(), "not-a-zip.zip") mustWrite(t, filePath, "plain text, not a zip archive") _, err := LoadArchive(filePath) if err == nil || !strings.Contains(err.Error(), "open pack archive") { t.Fatalf("LoadArchive() error = %v, want open archive error", err) } }