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

267 lines
7.2 KiB
Go

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