package releasekit_test import ( "context" "errors" "os" "os/exec" "path/filepath" "testing" releasekit "gitea.maximumdirect.net/eric/ReleaseKit" ) func TestPrepareTaggedCheckoutFetchesExpectedCommitAndDetaches(t *testing.T) { repository := newTaggedDeliveryConsumer(t) tagCommit := publishPreparationTag(t, repository) advancePreparationCheckout(t, repository, "later local commit") if err := releasekit.PrepareTaggedCheckout(context.Background(), releasekit.TaggedCheckoutRequest{ RepositoryDir: repository, Version: "v1.2.3", ExpectedCommit: tagCommit, }); err != nil { t.Fatal(err) } if head := buildGit(t, repository, "rev-parse", "HEAD"); head != tagCommit { t.Fatalf("prepared HEAD = %s, want %s", head, tagCommit) } if branch := buildGit(t, repository, "rev-parse", "--abbrev-ref", "HEAD"); branch != "HEAD" { t.Fatalf("prepared checkout branch = %q, want detached HEAD", branch) } } func TestPrepareTaggedCheckoutRejectsWrongCommitBeforeCheckout(t *testing.T) { repository := newTaggedDeliveryConsumer(t) tagCommit := publishPreparationTag(t, repository) currentCommit := advancePreparationCheckout(t, repository, "later local commit") remote := buildGit(t, repository, "remote", "get-url", "origin") buildGit(t, repository, "remote", "add", "candidate", remote) err := releasekit.PrepareTaggedCheckout(context.Background(), releasekit.TaggedCheckoutRequest{ RepositoryDir: repository, Version: "v1.2.3", Remote: "candidate", ExpectedCommit: currentCommit, }) if err == nil { t.Fatal("PrepareTaggedCheckout() succeeded for a tag that resolves to another commit") } if head := buildGit(t, repository, "rev-parse", "HEAD"); head != currentCommit { t.Fatalf("wrong-tag rejection changed HEAD to %s, want %s (tag commit %s)", head, currentCommit, tagCommit) } if branch := buildGit(t, repository, "rev-parse", "--abbrev-ref", "HEAD"); branch == "HEAD" { t.Fatal("wrong-tag rejection detached the checkout") } } func TestPrepareTaggedCheckoutReportsLocalCheckoutConflict(t *testing.T) { repository := newTaggedDeliveryConsumer(t) tagCommit := publishPreparationTag(t, repository) advancePreparationCheckout(t, repository, "later committed content") path := filepath.Join(repository, "cmd/app/main.go") if err := os.WriteFile(path, []byte("uncommitted conflicting content"), 0o600); err != nil { t.Fatal(err) } currentCommit := buildGit(t, repository, "rev-parse", "HEAD") err := releasekit.PrepareTaggedCheckout(context.Background(), releasekit.TaggedCheckoutRequest{ RepositoryDir: repository, Version: "v1.2.3", ExpectedCommit: tagCommit, }) if err == nil { t.Fatal("PrepareTaggedCheckout() succeeded despite a conflicting local edit") } if head := buildGit(t, repository, "rev-parse", "HEAD"); head != currentCommit { t.Fatalf("conflicting checkout changed HEAD to %s, want %s", head, currentCommit) } contents, err := os.ReadFile(path) if err != nil || string(contents) != "uncommitted conflicting content" { t.Fatalf("conflicting local file = %q, %v", contents, err) } } func TestPrepareTaggedCheckoutReportsConflictingLocalTag(t *testing.T) { repository := newTaggedDeliveryConsumer(t) publishPreparationTag(t, repository) currentCommit := advancePreparationCheckout(t, repository, "later local commit") buildGit(t, repository, "tag", "v1.2.3") err := releasekit.PrepareTaggedCheckout(context.Background(), releasekit.TaggedCheckoutRequest{ RepositoryDir: repository, Version: "v1.2.3", ExpectedCommit: buildGit(t, repository, "rev-parse", "HEAD~1"), }) if err == nil { t.Fatal("PrepareTaggedCheckout() replaced a conflicting local tag") } if head := buildGit(t, repository, "rev-parse", "HEAD"); head != currentCommit { t.Fatalf("conflicting tag fetch changed HEAD to %s, want %s", head, currentCommit) } if tagCommit := buildGit(t, repository, "rev-parse", "refs/tags/v1.2.3^{commit}"); tagCommit != currentCommit { t.Fatalf("conflicting local tag changed to %s, want %s", tagCommit, currentCommit) } } func TestPrepareTaggedCheckoutValidatesInputsBeforeFetching(t *testing.T) { repository := newTaggedDeliveryConsumer(t) publishPreparationTag(t, repository) for _, request := range []releasekit.TaggedCheckoutRequest{ {RepositoryDir: repository, Version: "not-a-version", ExpectedCommit: buildGit(t, repository, "rev-parse", "HEAD")}, {RepositoryDir: repository, Version: "v1.2.3"}, {RepositoryDir: repository, Version: "v1.2.3", ExpectedCommit: "not-an-object-id"}, } { err := releasekit.PrepareTaggedCheckout(context.Background(), request) if err == nil { t.Fatalf("PrepareTaggedCheckout(%#v) succeeded", request) } if _, err := buildGitOutput(repository, "show-ref", "--verify", "refs/tags/v1.2.3"); err == nil { t.Fatal("input validation fetched the tag before rejecting the request") } } if err := releasekit.PrepareTaggedCheckout(context.Background(), releasekit.TaggedCheckoutRequest{ RepositoryDir: repository, Version: "invalid", ExpectedCommit: "bad", }); !errors.Is(err, releasekit.ErrInvalidVersion) { t.Fatalf("invalid-version error = %v, want ErrInvalidVersion", err) } } func TestPrepareBuildDependenciesRequiresRepositoryDirectory(t *testing.T) { if err := releasekit.PrepareBuildDependencies(context.Background(), " "); err == nil { t.Fatal("PrepareBuildDependencies() accepted an empty repository directory") } } func publishPreparationTag(t *testing.T, repository string) string { t.Helper() remoteDir := t.TempDir() remote := filepath.Join(remoteDir, "origin.git") buildGit(t, remoteDir, "init", "--bare", remote) buildGit(t, repository, "remote", "add", "origin", remote) commit := buildGit(t, repository, "rev-parse", "refs/tags/v1.2.3^{commit}") buildGit(t, repository, "push", "origin", "refs/tags/v1.2.3") buildGit(t, repository, "tag", "-d", "v1.2.3") return commit } func advancePreparationCheckout(t *testing.T, repository, source string) string { t.Helper() path := filepath.Join(repository, "cmd/app/main.go") if err := os.WriteFile(path, []byte("package main\n// "+source+"\nfunc main() {}\n"), 0o600); err != nil { t.Fatal(err) } buildGit(t, repository, "add", "cmd/app/main.go") buildGit(t, repository, "commit", "-m", source) return buildGit(t, repository, "rev-parse", "HEAD") } func buildGitOutput(repository string, args ...string) ([]byte, error) { command := exec.Command("git", args...) command.Dir = repository return command.Output() }