Files
kandev/apps/backend/internal/common/gitbase/materialize_test.go
T

245 lines
9.2 KiB
Go

package gitbase
import (
"context"
"errors"
"strings"
"testing"
"github.com/kandev/kandev/internal/task/models"
)
type gitFake struct {
commands [][]string
outputs map[string]string
errors map[string]error
}
type gitCommandError struct {
code int
}
func (e gitCommandError) Error() string { return "git command failed" }
func (e gitCommandError) ExitCode() int { return e.code }
func (f *gitFake) run(_ context.Context, args ...string) (string, error) {
f.commands = append(f.commands, append([]string(nil), args...))
key := strings.Join(args, " ")
if err := f.errors[key]; err != nil {
return "", err
}
if output, ok := f.outputs[key]; ok {
return output, nil
}
if len(args) >= 3 && args[0] == "config" && args[1] == "--get" && strings.HasPrefix(args[2], "remote.") {
return "", gitCommandError{code: 1}
}
return "", nil
}
func TestMaterializeQualifiedBaseDoesNotReplaceRemoteAfterConfigReadFailure(t *testing.T) {
base := testPRBase()
remoteName := base.Target.ComparisonRemoteName()
readErr := errors.New("repository config is unreadable")
fake := &gitFake{
errors: map[string]error{"config --get remote." + remoteName + ".url": readErr},
outputs: map[string]string{},
}
_, err := Materialize(context.Background(), fake.run, base)
if ErrorCode(err) != ErrorRemoteSetup || !errors.Is(err, readErr) {
t.Fatalf("Materialize() error = %v, code = %q, want preserved config-read failure", err, ErrorCode(err))
}
if hasCommand(fake.commands, "remote", "add", "--no-tags", remoteName, base.Target.TargetRepository.RemoteURL) {
t.Fatalf("Materialize() replaced remote after config-read failure: %#v", fake.commands)
}
}
func testPRBase() models.PRBase {
return models.PRBase{Target: models.ComparisonTarget{
Version: models.ComparisonTargetVersion,
Provider: models.ComparisonTargetProviderGitHub,
Kind: models.ComparisonTargetKindPullRequest,
Number: 42,
HeadBranch: "feature/same-name",
TargetBranch: "release/next",
HeadRepository: models.ComparisonTargetRepository{
Host: "github.com", Path: "fork/widget", ProviderID: "fork-1",
RemoteURL: "https://github.com/fork/widget.git",
},
TargetRepository: models.ComparisonTargetRepository{
Host: "github.com", Path: "upstream/widget", ProviderID: "base-1",
RemoteURL: "https://github.com/upstream/widget.git",
},
}}
}
func TestMaterializeQualifiedBaseFetchesExactTargetAndVerifiesOID(t *testing.T) {
base := testPRBase()
base.OID = "0123456789abcdef0123456789abcdef01234567"
fake := &gitFake{outputs: map[string]string{
"rev-parse --verify " + base.Target.ComparisonRef() + "^{commit}": base.OID,
}, errors: map[string]error{}}
got, err := Materialize(context.Background(), fake.run, base)
if err != nil {
t.Fatalf("Materialize(): %v", err)
}
if got.Ref != base.Target.ComparisonRef() || got.OID != base.OID {
t.Fatalf("materialization = %#v, want qualified ref and observed OID", got)
}
wantFetch := []string{"fetch", "--no-tags", base.Target.ComparisonRemoteName(), "+refs/heads/release/next:" + base.Target.ComparisonRef()}
if !hasCommand(fake.commands, wantFetch...) {
t.Fatalf("fetch commands = %#v, missing %#v", fake.commands, wantFetch)
}
if hasCommand(fake.commands, "fetch", "--no-tags", "origin", "+refs/heads/release/next:release/next") {
t.Fatalf("materialized an unqualified origin branch: %#v", fake.commands)
}
}
func TestMaterializeQualifiedBaseUsesCheckoutSSHTransport(t *testing.T) {
base := testPRBase()
remoteName := base.Target.ComparisonRemoteName()
comparisonRef := base.Target.ComparisonRef()
headRef := "refs/remotes/" + remoteName + "/pull/42/head"
sshURL := "git@github.com:upstream/widget.git"
fake := &gitFake{
outputs: map[string]string{
"config --get remote.origin.url": "git@github.com:fork/widget.git",
"rev-parse --verify " + comparisonRef + "^{commit}": "0123456789abcdef0123456789abcdef01234567",
"rev-parse --verify " + headRef + "^{commit}": "fedcba9876543210fedcba9876543210fedcba98",
},
errors: map[string]error{},
}
got, err := Materialize(context.Background(), fake.run, base)
if err != nil {
t.Fatalf("Materialize(): %v", err)
}
if got.RemoteName != remoteName || got.Ref != comparisonRef ||
!hasCommand(fake.commands, "remote", "add", "--no-tags", remoteName, sshURL) {
t.Fatalf("materialization = %#v, commands = %#v; want SSH comparison remote %q at %q", got, fake.commands, remoteName, sshURL)
}
if !hasCommand(fake.commands, "fetch", "--no-tags", remoteName,
"+refs/heads/release/next:"+comparisonRef) {
t.Fatalf("fetch commands = %#v; want exact branch through SSH comparison remote", fake.commands)
}
head, err := FetchPullRequestHead(context.Background(), fake.run, base.Target)
if err != nil {
t.Fatalf("FetchPullRequestHead(): %v", err)
}
if head.Ref != headRef || head.OID != "fedcba9876543210fedcba9876543210fedcba98" ||
!hasCommand(fake.commands, "fetch", "--no-tags", remoteName, "+refs/pull/42/head:"+headRef) {
t.Fatalf("head snapshot = %#v, commands = %#v; want pull head fetched through the SSH comparison remote", head, fake.commands)
}
}
func TestMaterializeQualifiedBaseUpdatesSameRepositoryWhenSSHTransportChanges(t *testing.T) {
base := testPRBase()
remoteName := base.Target.ComparisonRemoteName()
sshURL := "git@github.com:upstream/widget.git"
fake := &gitFake{
outputs: map[string]string{
"config --get remote.origin.url": "git@github.com:fork/widget.git",
"config --get remote." + remoteName + ".url": base.Target.TargetRepository.RemoteURL,
"rev-parse --verify " + base.Target.ComparisonRef() + "^{commit}": "0123456789abcdef0123456789abcdef01234567",
},
errors: map[string]error{},
}
if _, err := Materialize(context.Background(), fake.run, base); err != nil {
t.Fatalf("Materialize(): %v", err)
}
if !hasCommand(fake.commands, "remote", "set-url", remoteName, sshURL) {
t.Fatalf("commands = %#v; want same-identity comparison remote updated to SSH", fake.commands)
}
if hasCommand(fake.commands, "remote", "add", "--no-tags", remoteName, sshURL) {
t.Fatalf("commands = %#v; existing comparison remote was added twice", fake.commands)
}
}
func TestMaterializeQualifiedBaseRejectsOIDDrift(t *testing.T) {
base := testPRBase()
base.OID = "0123456789abcdef0123456789abcdef01234567"
fake := &gitFake{outputs: map[string]string{
"rev-parse --verify " + base.Target.ComparisonRef() + "^{commit}": "fedcba9876543210fedcba9876543210fedcba98",
}, errors: map[string]error{}}
_, err := Materialize(context.Background(), fake.run, base)
if ErrorCode(err) != ErrorOIDMismatch {
t.Fatalf("ErrorCode() = %q, want %q (err=%v)", ErrorCode(err), ErrorOIDMismatch, err)
}
}
func TestMaterializeQualifiedBaseRejectsRemoteCollision(t *testing.T) {
base := testPRBase()
fake := &gitFake{
outputs: map[string]string{"config --get remote." + base.Target.ComparisonRemoteName() + ".url": "https://github.com/other/widget.git"},
errors: map[string]error{},
}
_, err := Materialize(context.Background(), fake.run, base)
if ErrorCode(err) != ErrorRemoteCollision {
t.Fatalf("ErrorCode() = %q, want %q (err=%v)", ErrorCode(err), ErrorRemoteCollision, err)
}
}
func TestMaterializeQualifiedBasePropagatesFetchAndAuthenticationErrors(t *testing.T) {
base := testPRBase()
remoteName := base.Target.ComparisonRemoteName()
authErr := errors.New("authentication failed")
fake := &gitFake{
errors: map[string]error{
"fetch --no-tags " + remoteName + " +refs/heads/release/next:" + base.Target.ComparisonRef(): authErr,
},
outputs: map[string]string{},
}
_, err := Materialize(context.Background(), fake.run, base)
if ErrorCode(err) != ErrorFetch || !errors.Is(err, authErr) {
t.Fatalf("Materialize() error = %v, code = %q, want propagated auth failure", err, ErrorCode(err))
}
}
func TestMaterializeQualifiedBasePreservesCancellation(t *testing.T) {
base := testPRBase()
canceled := context.Canceled
fake := &gitFake{errors: map[string]error{
"remote add --no-tags " + base.Target.ComparisonRemoteName() + " " + base.Target.TargetRepository.RemoteURL: canceled,
}, outputs: map[string]string{}}
_, err := Materialize(context.Background(), fake.run, base)
if !errors.Is(err, canceled) {
t.Fatalf("Materialize() error = %v, want context.Canceled", err)
}
}
func TestMaterializeQualifiedBaseFetchesPRHeadFromBaseRepository(t *testing.T) {
target := testPRBase().Target
headOID := "fedcba9876543210fedcba9876543210fedcba98"
wantRef := "refs/remotes/" + target.ComparisonRemoteName() + "/pull/42/head"
fake := &gitFake{outputs: map[string]string{
"rev-parse --verify " + wantRef + "^{commit}": headOID,
}, errors: map[string]error{}}
head, err := FetchPullRequestHead(context.Background(), fake.run, target)
if err != nil {
t.Fatalf("FetchPullRequestHead(): %v", err)
}
if head.Ref != wantRef || head.OID != headOID ||
!hasCommand(fake.commands, "fetch", "--no-tags", target.ComparisonRemoteName(), "+refs/pull/42/head:"+wantRef) {
t.Fatalf("head snapshot = %#v, commands = %#v", head, fake.commands)
}
}
func hasCommand(commands [][]string, want ...string) bool {
for _, command := range commands {
if len(command) != len(want) {
continue
}
match := true
for i := range want {
if command[i] != want[i] {
match = false
break
}
}
if match {
return true
}
}
return false
}