package source_test import ( "os" "os/exec" "path/filepath" "strings" "testing" "git.vakhrushev.me/av/convy/internal/source" ) func TestParseTellsTheTransportsApart(t *testing.T) { cases := []struct { raw string kind source.Kind location string rev string }{ {"../dev-conventions", source.Local, "../dev-conventions", ""}, {"/srv/conventions", source.Local, "/srv/conventions", ""}, {"https://git.example.org/av/conventions.git", source.Git, "https://git.example.org/av/conventions.git", ""}, {"http://git.example.org/av/conventions.git#v2", source.Git, "http://git.example.org/av/conventions.git", "v2"}, {"ssh://git@git.example.org:2222/av/conventions.git", source.Git, "ssh://git@git.example.org:2222/av/conventions.git", ""}, {"git@git.example.org:av/conventions.git", source.Git, "git@git.example.org:av/conventions.git", ""}, // A plain path is the directory as it lies; file:// is the same // repository as it is committed. {"file:///srv/conventions#main", source.Git, "file:///srv/conventions", "main"}, } for _, tc := range cases { t.Run(tc.raw, func(t *testing.T) { ref, err := source.Parse(tc.raw) if err != nil { t.Fatalf("parsing %q: %v", tc.raw, err) } if ref.Kind != tc.kind { t.Errorf("read as %s, expected %s", ref.Kind, tc.kind) } if ref.Location != tc.location { t.Errorf("location %q, expected %q", ref.Location, tc.location) } if ref.Rev != tc.rev { t.Errorf("revision %q, expected %q", ref.Rev, tc.rev) } }) } } func TestParseRefusesWhatItCannotMean(t *testing.T) { cases := []struct { name string raw string want string }{ {"nothing at all", " ", "empty"}, {"a revision of a directory", "../conventions#main", "only a git repository has"}, {"a home directory", "~/projects/conventions", "every other machine"}, {"a scheme nobody serves", "rclone://remote/conventions", "over git"}, {"a hash with no revision", "https://git.example.org/c.git#", "names no revision"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := source.Parse(tc.raw) if err == nil { t.Fatalf("the reference %q went through", tc.raw) } if !strings.Contains(err.Error(), tc.want) { t.Errorf("the refusal does not say %q: %s", tc.want, err) } }) } } func TestOpenResolvesAPathAgainstTheManifest(t *testing.T) { base := t.TempDir() if err := os.MkdirAll(filepath.Join(base, "vendor", "conventions"), 0o755); err != nil { t.Fatal(err) } ref, err := source.Parse("vendor/conventions") if err != nil { t.Fatal(err) } tree, err := source.Open(ref, base) if err != nil { t.Fatalf("opening a relative path: %v", err) } defer tree.Close() if tree.Dir() != filepath.Join(base, "vendor", "conventions") { t.Errorf("resolved to %s", tree.Dir()) } // Nothing was fetched, so nothing is released: the directory was there // before the command and stays after it. tree.Close() if _, err := os.Stat(tree.Dir()); err != nil { t.Errorf("closing removed a directory that was not fetched: %v", err) } } func TestOpenRefusesAFileWhereALevelIsExpected(t *testing.T) { base := t.TempDir() if err := os.WriteFile(filepath.Join(base, "conventions"), []byte("x"), 0o644); err != nil { t.Fatal(err) } ref, _ := source.Parse("conventions") if _, err := source.Open(ref, base); err == nil || !strings.Contains(err.Error(), "a level is a directory") { t.Errorf("a file passed as a level: %v", err) } } // A git reference takes what is committed rather than what lies in the working // tree, and that is the difference between the two spellings of a local suite. func TestOpenGitTakesTheCommittedState(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("no git in PATH") } repo := t.TempDir() name := filepath.Join(repo, "suite.toml") if err := os.WriteFile(name, []byte("committed\n"), 0o644); err != nil { t.Fatal(err) } for _, args := range [][]string{ {"init", "--quiet", "-b", "main"}, {"-c", "user.email=t@example.org", "-c", "user.name=t", "add", "suite.toml"}, {"-c", "user.email=t@example.org", "-c", "user.name=t", "commit", "--quiet", "-m", "first"}, } { cmd := exec.Command("git", args...) cmd.Dir = repo if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } } if err := os.WriteFile(name, []byte("uncommitted\n"), 0o644); err != nil { t.Fatal(err) } ref, err := source.Parse("file://" + repo + "#main") if err != nil { t.Fatal(err) } tree, err := source.Open(ref, ".") if err != nil { t.Fatalf("cloning a local repository: %v", err) } body, err := os.ReadFile(filepath.Join(tree.Dir(), "suite.toml")) if err != nil { t.Fatal(err) } if strings.TrimSpace(string(body)) != "committed" { t.Errorf("the working tree leaked into the clone: %q", body) } dir := tree.Dir() if err := tree.Close(); err != nil { t.Fatal(err) } if _, err := os.Stat(dir); err == nil { t.Errorf("the clone survived the close: %s", dir) } }