package gitops import ( "os" "path/filepath" "testing" ) func TestNormalizeRejectsUnsafeStackAndRepositoryArguments(t *testing.T) { cases := []Input{ {StackName: "../escape", RepoURL: "https://example.invalid/repo.git"}, {StackName: "demo", RepoURL: "--upload-pack=evil"}, {StackName: "demo", RepoURL: "https://example.invalid/repo.git\n--option"}, } for _, in := range cases { if err := normalize(&in); err == nil { t.Fatalf("expected unsafe input to be rejected: %#v", in) } } } func TestSyncManagedTreePreservesUnmanagedDataAndRemovesStaleGitFiles(t *testing.T) { dst := t.TempDir() stage1 := t.TempDir() if err := os.WriteFile(filepath.Join(stage1, "compose.yaml"), []byte("services: {}\n"), 0640); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(stage1, "config"), 0750); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(stage1, "config", "old.txt"), []byte("old"), 0640); err != nil { t.Fatal(err) } if err := syncManagedTree(stage1, dst); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(dst, "data"), 0750); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dst, "data", "runtime.db"), []byte("keep"), 0600); err != nil { t.Fatal(err) } stage2 := t.TempDir() if err := os.WriteFile(filepath.Join(stage2, "compose.yaml"), []byte("services:\n web:\n image: nginx\n"), 0640); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(stage2, "config"), 0750); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(stage2, "config", "new.txt"), []byte("new"), 0640); err != nil { t.Fatal(err) } if err := syncManagedTree(stage2, dst); err != nil { t.Fatal(err) } if b, err := os.ReadFile(filepath.Join(dst, "data", "runtime.db")); err != nil || string(b) != "keep" { t.Fatalf("unmanaged data was not preserved: %q %v", b, err) } if _, err := os.Stat(filepath.Join(dst, "config", "old.txt")); !os.IsNotExist(err) { t.Fatalf("stale Git-managed file still exists: %v", err) } if b, err := os.ReadFile(filepath.Join(dst, "config", "new.txt")); err != nil || string(b) != "new" { t.Fatalf("new Git file missing: %q %v", b, err) } } func TestSyncManagedTreeRejectsSymlinkDestination(t *testing.T) { parent := t.TempDir() outside := t.TempDir() dst := filepath.Join(parent, "demo") if err := os.Symlink(outside, dst); err != nil { t.Skipf("symlinks unavailable: %v", err) } stage := t.TempDir() if err := os.WriteFile(filepath.Join(stage, "compose.yaml"), []byte("services: {}\n"), 0640); err != nil { t.Fatal(err) } if err := syncManagedTree(stage, dst); err == nil { t.Fatal("expected symlink Git destination to be rejected") } }