284 lines
9.4 KiB
Go
284 lines
9.4 KiB
Go
package overlay
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/pack"
|
|
)
|
|
|
|
func TestApplyCopiesSourceAndAppliesPatch(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "backend", "hello.txt"), []byte("hello\n"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
packDir := t.TempDir()
|
|
patchBody := strings.Join([]string{
|
|
"diff --git a/backend/hello.txt b/backend/hello.txt",
|
|
"--- a/backend/hello.txt",
|
|
"+++ b/backend/hello.txt",
|
|
"@@ -1 +1 @@",
|
|
"-hello",
|
|
"+patched",
|
|
"",
|
|
}, "\n")
|
|
patchPath := filepath.Join(packDir, "overlays", "sample.patch")
|
|
if err := os.MkdirAll(filepath.Dir(patchPath), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(patchPath, []byte(patchBody), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
result, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: packDir,
|
|
SourceDir: sourceDir,
|
|
Overlays: []pack.HostOverlay{{
|
|
OverlayID: "sample",
|
|
PatchPath: "overlays/sample.patch",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Apply() error = %v", err)
|
|
}
|
|
body, err := os.ReadFile(filepath.Join(result.OutputDir, "backend", "hello.txt"))
|
|
if err != nil {
|
|
t.Fatalf("ReadFile() error = %v", err)
|
|
}
|
|
if string(body) != "patched\n" {
|
|
t.Fatalf("patched file = %q, want %q", string(body), "patched\n")
|
|
}
|
|
if _, err := os.Stat(result.MetadataFilePath); err != nil {
|
|
t.Fatalf("Stat(metadata) error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestApplySupportsRelativePackDir(t *testing.T) {
|
|
workspaceDir := t.TempDir()
|
|
originalWD, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Getwd() error = %v", err)
|
|
}
|
|
if err := os.Chdir(workspaceDir); err != nil {
|
|
t.Fatalf("Chdir() error = %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = os.Chdir(originalWD)
|
|
})
|
|
|
|
sourceDir := filepath.Join(workspaceDir, "source")
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "backend", "hello.txt"), []byte("hello\n"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
packDir := filepath.Join(workspaceDir, "pack")
|
|
patchPath := filepath.Join(packDir, "overlays", "sample.patch")
|
|
if err := os.MkdirAll(filepath.Dir(patchPath), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
patchBody := strings.Join([]string{
|
|
"diff --git a/backend/hello.txt b/backend/hello.txt",
|
|
"--- a/backend/hello.txt",
|
|
"+++ b/backend/hello.txt",
|
|
"@@ -1 +1 @@",
|
|
"-hello",
|
|
"+patched",
|
|
"",
|
|
}, "\n")
|
|
if err := os.WriteFile(patchPath, []byte(patchBody), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
result, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: "pack",
|
|
SourceDir: sourceDir,
|
|
Overlays: []pack.HostOverlay{{
|
|
OverlayID: "sample",
|
|
PatchPath: "overlays/sample.patch",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Apply() with relative pack dir error = %v", err)
|
|
}
|
|
body, err := os.ReadFile(filepath.Join(result.OutputDir, "backend", "hello.txt"))
|
|
if err != nil {
|
|
t.Fatalf("ReadFile() error = %v", err)
|
|
}
|
|
if string(body) != "patched\n" {
|
|
t.Fatalf("patched file = %q, want %q", string(body), "patched\n")
|
|
}
|
|
}
|
|
|
|
func TestFilterOverlaysRejectsMissingOverlayID(t *testing.T) {
|
|
_, err := FilterOverlays([]pack.HostOverlay{{OverlayID: "sample"}}, "missing")
|
|
if err == nil || !strings.Contains(err.Error(), `overlay "missing" did not match`) {
|
|
t.Fatalf("FilterOverlays() error = %v, want missing overlay detail", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyRejectsNestedOutputDir(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: sourceDir,
|
|
OutputDir: filepath.Join(sourceDir, "nested-output"),
|
|
Overlays: []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "must not be nested inside source dir") {
|
|
t.Fatalf("Apply() error = %v, want nested output rejection", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyPatchFileRejectsMissingPatch(t *testing.T) {
|
|
err := applyPatchFile(context.Background(), t.TempDir(), filepath.Join(t.TempDir(), "missing.patch"))
|
|
if err == nil || !strings.Contains(err.Error(), "stat patch file") {
|
|
t.Fatalf("applyPatchFile() error = %v, want missing patch stat error", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyPatchFileRejectsInvalidPatch(t *testing.T) {
|
|
outputDir := t.TempDir()
|
|
patchPath := filepath.Join(t.TempDir(), "invalid.patch")
|
|
if err := os.WriteFile(patchPath, []byte("not a patch\n"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
err := applyPatchFile(context.Background(), outputDir, patchPath)
|
|
if err == nil {
|
|
t.Fatal("applyPatchFile() error = nil, want invalid patch failure")
|
|
}
|
|
}
|
|
|
|
func TestWriteMetadataIncludesSourceDirAndOverlays(t *testing.T) {
|
|
metadataPath := filepath.Join(t.TempDir(), metadataFileName)
|
|
overlays := []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}}
|
|
if err := writeMetadata(metadataPath, "/tmp/source", overlays); err != nil {
|
|
t.Fatalf("writeMetadata() error = %v", err)
|
|
}
|
|
|
|
body, err := os.ReadFile(metadataPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile() error = %v", err)
|
|
}
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(body, &decoded); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
|
}
|
|
if got, _ := decoded["source_dir"].(string); got != "/tmp/source" {
|
|
t.Fatalf("source_dir = %q, want %q", got, "/tmp/source")
|
|
}
|
|
applied, ok := decoded["applied_overlays"].([]any)
|
|
if !ok || len(applied) != 1 {
|
|
t.Fatalf("applied_overlays = %#v, want one overlay", decoded["applied_overlays"])
|
|
}
|
|
}
|
|
|
|
func TestCopyTreeSkipsGitAndPreservesSymlink(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
outputDir := filepath.Join(t.TempDir(), "output")
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, ".git", "objects"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(.git) error = %v", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(backend) error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, ".git", "config"), []byte("ignored"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(.git/config) error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "backend", "hello.txt"), []byte("hello\n"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(hello.txt) error = %v", err)
|
|
}
|
|
if err := os.Symlink(filepath.Join("backend", "hello.txt"), filepath.Join(sourceDir, "hello-link")); err != nil {
|
|
t.Fatalf("Symlink() error = %v", err)
|
|
}
|
|
|
|
if err := copyTree(sourceDir, outputDir); err != nil {
|
|
t.Fatalf("copyTree() error = %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(outputDir, ".git", "config")); !os.IsNotExist(err) {
|
|
t.Fatalf("output .git/config error = %v, want not exist", err)
|
|
}
|
|
target, err := os.Readlink(filepath.Join(outputDir, "hello-link"))
|
|
if err != nil {
|
|
t.Fatalf("Readlink() error = %v", err)
|
|
}
|
|
if target != filepath.Join("backend", "hello.txt") {
|
|
t.Fatalf("symlink target = %q, want backend/hello.txt", target)
|
|
}
|
|
}
|
|
|
|
func TestApplyRejectsExistingOutputDir(t *testing.T) {
|
|
sourceDir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
outputDir := filepath.Join(t.TempDir(), "existing-output")
|
|
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(outputDir) error = %v", err)
|
|
}
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: sourceDir,
|
|
OutputDir: outputDir,
|
|
Overlays: []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "already exists") {
|
|
t.Fatalf("Apply() error = %v, want existing output rejection", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyRejectsSourceFile(t *testing.T) {
|
|
sourceDir := filepath.Join(t.TempDir(), "source.txt")
|
|
if err := os.WriteFile(sourceDir, []byte("not a dir"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
_, err := Apply(context.Background(), ApplyRequest{
|
|
PackDir: t.TempDir(),
|
|
SourceDir: sourceDir,
|
|
Overlays: []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "must be a directory") {
|
|
t.Fatalf("Apply() error = %v, want source dir rejection", err)
|
|
}
|
|
}
|
|
|
|
func TestCopyFileRejectsMissingSource(t *testing.T) {
|
|
err := copyFile(filepath.Join(t.TempDir(), "missing.txt"), filepath.Join(t.TempDir(), "target.txt"), 0o644)
|
|
if err == nil {
|
|
t.Fatal("copyFile() error = nil, want missing source failure")
|
|
}
|
|
}
|
|
|
|
func TestCopyFileRejectsTargetParentThatIsAFile(t *testing.T) {
|
|
parentFile := filepath.Join(t.TempDir(), "parent-file")
|
|
if err := os.WriteFile(parentFile, []byte("block mkdir"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(parentFile) error = %v", err)
|
|
}
|
|
sourcePath := filepath.Join(t.TempDir(), "source.txt")
|
|
if err := os.WriteFile(sourcePath, []byte("hello"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(sourcePath) error = %v", err)
|
|
}
|
|
|
|
err := copyFile(sourcePath, filepath.Join(parentFile, "nested", "target.txt"), 0o644)
|
|
if err == nil {
|
|
t.Fatal("copyFile() error = nil, want parent path mkdir failure")
|
|
}
|
|
}
|