package spindle
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func gitInDir(t *testing.T, dir string, args ...string) string {
t.Helper()
out, err := exec.Command("git", append([]string{"-C", dir}, args...)...).CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
func loadPipelineAt(t *testing.T, source string) (names []string) {
t.Helper()
sha := gitInDir(t, source, "rev-parse", "HEAD")
checkout := filepath.Join(t.TempDir(), "checkout")
raw, commit, err := (&Spindle{}).loadPipeline(context.Background(), source, checkout, sha)
if err != nil {
t.Fatal(err)
}
if commit != sha {
t.Errorf("commit = %s, want %s", commit, sha)
}
for _, w := range raw {
if strings.Contains(string(w.Contents), "stolen") {
t.Errorf("%s holds a file from outside the repo", w.Name)
}
names = append(names, w.Name)
}
return names
}
func newSourceRepo(t *testing.T, format string) string {
t.Helper()
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
}
dir := filepath.Join(t.TempDir(), "source")
gitInDir(t, filepath.Dir(dir), "init", "-q", "-b", "main", "--object-format="+format, dir)
gitInDir(t, dir, "config", "user.email", "ci@nel.pet")
gitInDir(t, dir, "config", "user.name", "ci")
return dir
}
func commitEverything(t *testing.T, dir string) {
t.Helper()
gitInDir(t, dir, "add", "-A")
gitInDir(t, dir, "commit", "-q", "-m", "wip")
}
func writeTestFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
func TestLoadPipelineReadsRegularFilesOnly(t *testing.T) {
outside := t.TempDir()
writeTestFile(t, filepath.Join(outside, "secret.yaml"), "stolen\n")
src := newSourceRepo(t, "sha1")
writeTestFile(t, filepath.Join(src, ".tangled/workflows/ci.yaml"), "when: []\n")
writeTestFile(t, filepath.Join(src, ".tangled/workflows/nested/deep.yaml"), "when: []\n")
if err := os.Symlink(filepath.Join(outside, "secret.yaml"), filepath.Join(src, ".tangled/workflows/link.yaml")); err != nil {
t.Fatal(err)
}
commitEverything(t, src)
if got := loadPipelineAt(t, src); len(got) != 1 || got[0] != "ci.yaml" {
t.Errorf("workflows = %v, want only the regular file ci.yaml", got)
}
}
func TestLoadPipelineIgnoresSymlinkedWorkflowDirs(t *testing.T) {
outside := t.TempDir()
writeTestFile(t, filepath.Join(outside, "workflows/evil.yaml"), "stolen\n")
writeTestFile(t, filepath.Join(outside, "evil.yaml"), "stolen\n")
t.Run("tangled dir is a symlink", func(t *testing.T) {
src := newSourceRepo(t, "sha1")
writeTestFile(t, filepath.Join(src, "README.md"), "hi\n")
if err := os.Symlink(outside, filepath.Join(src, ".tangled")); err != nil {
t.Fatal(err)
}
commitEverything(t, src)
if got := loadPipelineAt(t, src); len(got) != 0 {
t.Errorf("workflows = %v, want none", got)
}
})
t.Run("workflows dir is a symlink", func(t *testing.T) {
src := newSourceRepo(t, "sha1")
if err := os.MkdirAll(filepath.Join(src, ".tangled"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outside, filepath.Join(src, ".tangled/workflows")); err != nil {
t.Fatal(err)
}
commitEverything(t, src)
if got := loadPipelineAt(t, src); len(got) != 0 {
t.Errorf("workflows = %v, want none", got)
}
})
}