package buildstep import ( "context" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/forgekey" ) const sha = "0123456789abcdef0123456789abcdef01234567" // fakeGit records every command and answers by a rule. type fakeGit struct { calls []string envs [][]string rule func(args string) forgekey.Ran } func (f *fakeGit) run(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { line := name + " " + strings.Join(args, " ") f.calls = append(f.calls, line) f.envs = append(f.envs, env) if f.rule != nil { if r := f.rule(line); r.Status != 0 || r.Stdout != "" || r.Err != "" { return r } } if len(args) > 4 && args[4] == "rev-parse" { return forgekey.Ran{Stdout: sha + "\n"} } return forgekey.Ran{} } const pre = "git -c credential.helper= -C /w " // 1474/AC1: the git commands are exactly the sequence, none with a credential or an https remote. func TestFetchCommitRunsTheSequence(t *testing.T) { f := &fakeGit{} if err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/novox/mesh-sdk.git", sha, ""); err != nil { t.Fatal(err) } want := []string{ pre + "init -q", pre + "remote add origin ssh://mesh-forge/novox/mesh-sdk.git", pre + "fetch --no-tags --depth 1 origin " + sha, pre + "checkout -q --detach " + sha, pre + "rev-parse HEAD", } if strings.Join(f.calls, "\n") != strings.Join(want, "\n") { t.Errorf("commands:\n%s\nwant:\n%s", strings.Join(f.calls, "\n"), strings.Join(want, "\n")) } for i, c := range f.calls { if strings.Contains(c, "https://") || strings.Contains(c, "@") || !strings.Contains(c, "credential.helper= ") { t.Errorf("command %q carries a credential or https", c) } if len(f.envs[i]) != 1 || f.envs[i][0] != "GIT_TERMINAL_PROMPT=0" { t.Errorf("env %v", f.envs[i]) } } } // 1474/AC2: the depth-1 fetch fails and a pull ref is given: the ref is fetched and the commit verified; with no // ref, the fetch's failure is the answer. func TestFetchCommitFallsBackToThePullRef(t *testing.T) { f := &fakeGit{rule: func(l string) forgekey.Ran { if strings.Contains(l, "--depth 1") { return forgekey.Ran{Status: 128, Stderr: "fatal: couldn't find remote ref " + sha + "\n"} } return forgekey.Ran{} }} if err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/o/r.git", sha, "refs/pull/1/head"); err != nil { t.Fatal(err) } want := []string{pre + "fetch --no-tags origin refs/pull/1/head", pre + "cat-file -e " + sha + "^{commit}", pre + "checkout -q --detach " + sha} got := strings.Join(f.calls[3:6], "\n") if got != strings.Join(want, "\n") { t.Errorf("after the failed fetch:\n%s\nwant:\n%s", got, strings.Join(want, "\n")) } g := &fakeGit{rule: f.rule} err := FetchCommit(context.Background(), g.run, "/w", "ssh://mesh-forge/o/r.git", sha, "") if err == nil || !strings.Contains(err.Error(), "couldn't find remote ref") || len(g.calls) != 3 { t.Errorf("without a ref: %v after %d commands", err, len(g.calls)) } h := &fakeGit{rule: func(l string) forgekey.Ran { if strings.Contains(l, "--depth 1") || strings.Contains(l, "cat-file") { return forgekey.Ran{Status: 1, Stderr: "no"} } return forgekey.Ran{} }} if err := FetchCommit(context.Background(), h.run, "/w", "ssh://mesh-forge/o/r.git", sha, "refs/pull/1/head"); err == nil { t.Error("a commit that is not in the ref was accepted") } } // 1474/AC3: HEAD differs from the commit after the checkout: an error naming both. func TestFetchCommitAnswersAHeadThatIsNotTheCommit(t *testing.T) { other := strings.Repeat("a", 40) f := &fakeGit{rule: func(l string) forgekey.Ran { if strings.HasSuffix(l, "rev-parse HEAD") { return forgekey.Ran{Stdout: other + "\n"} } return forgekey.Ran{} }} err := FetchCommit(context.Background(), f.run, "/w", "ssh://mesh-forge/o/r.git", sha, "") if err == nil || !strings.Contains(err.Error(), other) || !strings.Contains(err.Error(), sha) { t.Errorf("error = %v, want both shas", err) } } // 1474/AC4: an https remote, user information, a file remote without the option, or a commit that is not 40 // hex digits is refused before anything runs. func TestFetchCommitRefusesBeforeRunningAnything(t *testing.T) { for _, c := range []struct{ remote, commit, ref string }{ {"https://forge.example/o/r.git", sha, ""}, {"http://forge.example/o/r.git", sha, ""}, {"ssh://git@mesh-forge/o/r.git", sha, ""}, {"ssh://user:pw@mesh-forge/o/r.git", sha, ""}, {"file:///tmp/r.git", sha, ""}, {"/tmp/r.git", sha, ""}, {"ssh://mesh-forge/o/r.git", "abc123", ""}, {"ssh://mesh-forge/o/r.git", strings.ToUpper(sha), ""}, {"ssh://mesh-forge/o/r.git", "main", ""}, {"ssh://mesh-forge/o/r.git", sha, "--upload-pack=x"}, } { f := &fakeGit{} if err := FetchCommit(context.Background(), f.run, "/w", c.remote, c.commit, c.ref); err == nil || len(f.calls) != 0 { t.Errorf("%+v: err %v, %d commands", c, err, len(f.calls)) } } f := &fakeGit{} if err := FetchCommit(context.Background(), f.run, "/w", "file:///tmp/r.git", sha, "", AllowFileRemote()); err != nil { t.Errorf("a file remote with the option: %v", err) } }