test(project): achieve ≥70% package coverage across all internal packages
- store/sqlite: 75.4% (repos + db coverage) - host/sub2api: 80.8% (httptest mock server, pure function tests) - app: 74.2% (handler error paths, NewActionSet closures) - pack: 72.4% - provision: 75.2% - access: 77.3% - config: 94.7% (lookup mock tests) All tests pass: build, vet, race, coverage gates.
This commit is contained in:
266
internal/pack/extra_test.go
Normal file
266
internal/pack/extra_test.go
Normal file
@@ -0,0 +1,266 @@
|
||||
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: "api",
|
||||
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: "api",
|
||||
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: "api",
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user