Files
ReleaseKit/internal/validation/destination_test.go

116 lines
5.2 KiB
Go

package validation
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
func TestResolveDestinationClassifiesCommittedPaths(t *testing.T) {
repository := newTreeRepository(t)
writeTreeFile(t, repository, "README.md", []byte("root\n"))
writeTreeFile(t, repository, "docs/page.md", []byte("source\n"))
writeTreeFile(t, repository, "docs/target.md", []byte("target\n"))
writeTreeFile(t, repository, "docs/nested/item.txt", []byte("nested\n"))
writeTreeFile(t, repository, "docs/dir/item.txt", []byte("directory\n"))
for name, target := range map[string]string{
"docs/inside": "target.md",
"docs/through-dir": "nested",
"docs/outside": "../../neighbor/file",
"docs/absolute": "/outside/file",
"docs/dangling": "missing-target",
"docs/cycle-a": "cycle-b",
"docs/cycle-b": "cycle-a",
} {
if err := os.Symlink(target, filepath.Join(repository, filepath.FromSlash(name))); err != nil {
t.Fatal(err)
}
}
submoduleSource := newTreeRepository(t)
writeTreeFile(t, submoduleSource, "sub.txt", []byte("submodule\n"))
gitTree(t, submoduleSource, "add", "--all")
gitTree(t, submoduleSource, "commit", "-m", "create submodule source")
submoduleOID := strings.TrimSpace(gitTree(t, submoduleSource, "rev-parse", "HEAD"))
gitTree(t, repository, "add", "--all")
gitTree(t, repository, "update-index", "--add", "--cacheinfo", "160000,"+submoduleOID+",docs/submodule")
gitTree(t, repository, "commit", "-m", "capture destination fixtures")
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
tree, err := ReadCandidateTree(context.Background(), repository, commit)
if err != nil {
t.Fatal(err)
}
if err := os.Remove(filepath.Join(repository, "docs", "inside")); err != nil {
t.Fatal(err)
}
if err := os.Symlink("../../neighbor/file", filepath.Join(repository, "docs", "inside")); err != nil {
t.Fatal(err)
}
cases := []struct {
name string
source string
destination string
kind DestinationKind
path string
}{
{name: "relative file", source: "docs/page.md", destination: "target.md", kind: DestinationEntry, path: "docs/target.md"},
{name: "parent traversal inside root", source: "docs/page.md", destination: "../README.md", kind: DestinationEntry, path: "README.md"},
{name: "outside root", source: "docs/page.md", destination: "../../neighbor/file", kind: DestinationOutsideCheckout, path: "../neighbor/file"},
{name: "repository root directory", source: "docs/page.md", destination: "..", kind: DestinationDirectory, path: "."},
{name: "directory", source: "docs/page.md", destination: "dir", kind: DestinationDirectory, path: "docs/dir"},
{name: "symlink to file", source: "docs/page.md", destination: "inside", kind: DestinationEntry, path: "docs/target.md"},
{name: "symlinked directory", source: "docs/page.md", destination: "through-dir/item.txt", kind: DestinationEntry, path: "docs/nested/item.txt"},
{name: "absolute symlink", source: "docs/page.md", destination: "absolute", kind: DestinationOutsideCheckout, path: "/outside/file"},
{name: "missing entry", source: "docs/page.md", destination: "missing", kind: DestinationMissing, path: "docs/missing"},
{name: "dangling symlink", source: "docs/page.md", destination: "dangling", kind: DestinationMissing, path: "docs/missing-target"},
{name: "symlink cycle", source: "docs/page.md", destination: "cycle-a", kind: DestinationSymlinkCycle, path: "docs/cycle-a"},
{name: "submodule contents", source: "docs/page.md", destination: "submodule/child", kind: DestinationSubmodule, path: "docs/submodule/child"},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
got, err := tree.ResolveDestination(context.Background(), test.source, test.destination)
if err != nil {
t.Fatalf("ResolveDestination() error = %v", err)
}
if got.Kind != test.kind || got.Path != test.path {
t.Fatalf("ResolveDestination() = %#v, want kind %q path %q", got, test.kind, test.path)
}
})
}
}
func TestResolveDestinationIgnoresNeighboringFiles(t *testing.T) {
repository := newTreeRepository(t)
writeTreeFile(t, repository, "docs/page.md", []byte("source\n"))
if err := os.Symlink("../../neighbor/file", filepath.Join(repository, "docs", "outside")); err != nil {
t.Fatal(err)
}
gitTree(t, repository, "add", "--all")
gitTree(t, repository, "commit", "-m", "capture outside link")
commit := strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD"))
tree, err := ReadCandidateTree(context.Background(), repository, commit)
if err != nil {
t.Fatal(err)
}
gotWithoutNeighbor, err := tree.ResolveDestination(context.Background(), "docs/page.md", "outside")
if err != nil {
t.Fatal(err)
}
neighbor := filepath.Join(filepath.Dir(repository), "neighbor", "file")
if err := os.MkdirAll(filepath.Dir(neighbor), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(neighbor, []byte("outside source\n"), 0o644); err != nil {
t.Fatal(err)
}
gotWithNeighbor, err := tree.ResolveDestination(context.Background(), "docs/page.md", "outside")
if err != nil {
t.Fatal(err)
}
if gotWithNeighbor != gotWithoutNeighbor || gotWithNeighbor.Kind != DestinationOutsideCheckout {
t.Fatalf("neighbor changed resolution: absent=%#v present=%#v", gotWithoutNeighbor, gotWithNeighbor)
}
}