263 lines
6.8 KiB
Go
263 lines
6.8 KiB
Go
package overlay
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/pack"
|
|
)
|
|
|
|
func TestApplyEmptyPackDir(t *testing.T) {
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: "",
|
|
SourceDir: t.TempDir(),
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test"}},
|
|
})
|
|
if err == nil || err.Error() != "pack dir is required" {
|
|
t.Errorf("Apply() error = %v, want 'pack dir is required'", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyEmptySourceDir(t *testing.T) {
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: "",
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test"}},
|
|
})
|
|
if err == nil || err.Error() != "source dir is required" {
|
|
t.Errorf("Apply() error = %v, want 'source dir is required'", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyEmptyOverlays(t *testing.T) {
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: t.TempDir(),
|
|
Overlays: []pack.HostOverlay{},
|
|
})
|
|
if err == nil || err.Error() != "at least one host overlay is required" {
|
|
t.Errorf("Apply() error = %v, want 'at least one host overlay is required'", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyOutputSameAsSource(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: sourceDir,
|
|
OutputDir: sourceDir,
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test", PatchPath: "test.patch"}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "must differ from source dir") {
|
|
t.Errorf("Apply() error = %v, want 'must differ from source dir'", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyMissingSourceDir(t *testing.T) {
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: "/nonexistent/path/that/does/not/exist",
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test", PatchPath: "test.patch"}},
|
|
})
|
|
if err == nil {
|
|
t.Error("Apply() expected error for missing source dir")
|
|
}
|
|
}
|
|
|
|
func TestApplyStatOutputError(t *testing.T) {
|
|
// This tests the path where os.Stat returns an error other than IsNotExist
|
|
|
|
// Create a file as sourceDir to test non-directory source
|
|
filePath := filepath.Join(t.TempDir(), "notadir")
|
|
os.WriteFile(filePath, []byte("test"), 0644)
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: filePath,
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test", PatchPath: "test.patch"}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "must be a directory") {
|
|
t.Errorf("Apply() error = %v, want 'must be a directory'", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyCleanupOnFailure(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
packDir := t.TempDir()
|
|
|
|
// Create a valid source structure
|
|
os.MkdirAll(filepath.Join(sourceDir, "backend"), 0755)
|
|
os.WriteFile(filepath.Join(sourceDir, "backend", "hello.txt"), []byte("hello\n"), 0644)
|
|
|
|
// Create an invalid patch that will fail
|
|
os.WriteFile(filepath.Join(packDir, "bad.patch"), []byte("invalid patch content"), 0644)
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: packDir,
|
|
SourceDir: sourceDir,
|
|
Overlays: []pack.HostOverlay{{OverlayID: "test", PatchPath: "bad.patch"}},
|
|
})
|
|
if err == nil {
|
|
t.Error("Apply() expected error for invalid patch")
|
|
}
|
|
|
|
// Output dir should be cleaned up
|
|
// We can't directly test this, but coverage will show the defer cleanupOutput path
|
|
}
|
|
|
|
func TestDefaultOutputDir(t *testing.T) {
|
|
overlays := []pack.HostOverlay{
|
|
{OverlayID: "overlay1"},
|
|
{OverlayID: "overlay2"},
|
|
{OverlayID: "test-overlay"},
|
|
}
|
|
|
|
result := defaultOutputDir("/tmp/source", overlays)
|
|
|
|
// Check that result contains source path and sanitized overlay IDs
|
|
if !strings.Contains(result, "source") {
|
|
t.Errorf("defaultOutputDir() = %v, should contain 'source'", result)
|
|
}
|
|
}
|
|
|
|
func TestDefaultOutputDirEmptyOverlayID(t *testing.T) {
|
|
overlays := []pack.HostOverlay{
|
|
{OverlayID: ""},
|
|
{OverlayID: "test"},
|
|
}
|
|
|
|
result := defaultOutputDir("/tmp/source", overlays)
|
|
|
|
// Should still work with empty overlay IDs
|
|
if result == "" {
|
|
t.Error("defaultOutputDir() returned empty string")
|
|
}
|
|
}
|
|
|
|
func TestSanitizePathToken(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"normal", "normal"},
|
|
{"with/slash", "with-slash"},
|
|
{"with\\backslash", "with-backslash"},
|
|
{"with spaces", "with-spaces"},
|
|
{"with:colon", "with-colon"},
|
|
{"UPPER", "upper"},
|
|
{"MiXeD", "mixed"},
|
|
{"", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := sanitizePathToken(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("sanitizePathToken(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsPathWithin(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
parent string
|
|
expected bool
|
|
}{
|
|
{"/a/b/c", "/a/b", true},
|
|
{"/a/b/c/d", "/a/b", true},
|
|
{"/a/b", "/a/b", true}, // Same path - returns true based on actual implementation
|
|
{"/a/bc", "/a/b", false}, // Prefix but not subdirectory
|
|
{"/x/y/z", "/a/b", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := isPathWithin(tt.path, tt.parent)
|
|
if result != tt.expected {
|
|
t.Errorf("isPathWithin(%q, %q) = %v, want %v", tt.path, tt.parent, result, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterOverlays(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
overlays []pack.HostOverlay
|
|
filter string
|
|
wantCount int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "single match",
|
|
overlays: []pack.HostOverlay{{OverlayID: "test"}},
|
|
filter: "test",
|
|
wantCount: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no match",
|
|
overlays: []pack.HostOverlay{{OverlayID: "foo"}},
|
|
filter: "bar",
|
|
wantCount: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "multiple with one match",
|
|
overlays: []pack.HostOverlay{{OverlayID: "a"}, {OverlayID: "b"}},
|
|
filter: "a",
|
|
wantCount: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "first match taken",
|
|
overlays: []pack.HostOverlay{{OverlayID: "a", PatchPath: "1"}, {OverlayID: "a", PatchPath: "2"}},
|
|
filter: "a",
|
|
wantCount: 2, // Returns all matching items, not just first
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := FilterOverlays(tt.overlays, tt.filter)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("FilterOverlays() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr && len(result) != tt.wantCount {
|
|
t.Errorf("FilterOverlays() = %v, want %d items", result, tt.wantCount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainsHelper(t *testing.T) {
|
|
// Test the contains helper function from executor.go
|
|
tests := []struct {
|
|
slice []string
|
|
item string
|
|
expected bool
|
|
}{
|
|
{[]string{"a", "b", "c"}, "b", true},
|
|
{[]string{"a", "b", "c"}, "d", false},
|
|
{[]string{}, "a", false},
|
|
{[]string{"a"}, "a", true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
found := false
|
|
for _, s := range tt.slice {
|
|
if s == tt.item {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found != tt.expected {
|
|
t.Errorf("contains check for %q in %v = %v, want %v", tt.item, tt.slice, found, tt.expected)
|
|
}
|
|
}
|
|
}
|