//go:build crossrepo package cli_test import ( "context" "fmt" "net/http" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/barerepo/server/testserver" ) var binary string func TestMain(m *testing.M) { dir, err := os.MkdirTemp("", "br-e2e-") if err != nil { panic(err) } binary = filepath.Join(dir, "br") if out, err := exec.Command("go", "build", "-o", binary, "./cmd/br").CombinedOutput(); err != nil { fmt.Fprintf(os.Stderr, "building br: %v\n%s", err, out) os.Exit(1) } code := m.Run() os.RemoveAll(dir) os.Exit(code) } func needs(t *testing.T, names ...string) { t.Helper() for _, name := range names { if _, err := exec.LookPath(name); err != nil { t.Skipf("%s is not installed", name) } } } func run(t *testing.T, dir, name string, args ...string) string { t.Helper() out, err := try(t, dir, name, args...) if err != nil { t.Fatalf("%s %s: %v\n%s", name, strings.Join(args, " "), err, out) } return out } func try(t *testing.T, dir, name string, args ...string) (string, error) { t.Helper() cmd := exec.Command(name, args...) cmd.Dir = dir cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=tester", "GIT_AUTHOR_EMAIL=t@x", "GIT_COMMITTER_NAME=tester", "GIT_COMMITTER_EMAIL=t@x", "GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_COUNT=1", "GIT_CONFIG_KEY_0=credential.helper", "GIT_CONFIG_VALUE_0=") out, err := cmd.CombinedOutput() return strings.TrimSpace(string(out)), err } // br talks to a real barerepo, so only a test that runs both halves catches them drifting apart. func TestBrDrivesARealServer(t *testing.T) { needs(t, "git", "ssh-keygen", "go") in := testserver.New(t) john := in.Account(t, "john") in.Repo(t, john, "john", "johnbot") work := filepath.Join(t.TempDir(), "c") run(t, "", "git", "clone", "-q", in.URLFor(john, "/john/johnbot"), work) run(t, work, "git", "checkout", "-q", "-b", "my-fix") if err := os.WriteFile(filepath.Join(work, "config.go"), []byte("package main\n\nfunc main() {}\n"), 0o600); err != nil { t.Fatal(err) } run(t, work, "git", "commit", "-qam", "fix the spin") if out := run(t, work, binary, "propose"); !strings.Contains(out, "proposal 1 opened") { t.Fatalf("br propose did not open a proposal: %s", out) } other := filepath.Join(t.TempDir(), "r") run(t, "", "git", "clone", "-q", in.URLFor(john, "/john/johnbot"), other) if out := run(t, other, binary, "fetch", "1"); !strings.Contains(out, "prop-1") { t.Errorf("br fetch did not make prop-1: %s", out) } if got := run(t, other, "git", "log", "-1", "--format=%s", "prop-1"); got != "fix the spin" { t.Errorf("prop-1 is at %q, want the proposed commit", got) } if out := run(t, other, binary, "notes"); !strings.Contains(out, "refs/notes/threads/1") { t.Errorf("br notes did not fetch the proposal's thread: %s", out) } if out := run(t, other, binary, "threads"); !strings.Contains(out, "git log --show-notes=threads/1") { t.Errorf("br threads did not list thread 1: %s", out) } run(t, other, binary, "reply", "1", "-m", "reads fine to me") run(t, work, binary, "notes") if out := run(t, work, binary, "thread", "1"); !strings.Contains(out, "reads fine to me") { t.Errorf("a reply pushed by br did not come back: %s", out) } } // br auth is the one protocol the client speaks alone, so it is the one that can drift unseen. func TestBrAuthTradesASignatureForOneSignInLink(t *testing.T) { needs(t, "git", "ssh-keygen", "go") in := testserver.New(t) home := t.TempDir() if err := os.MkdirAll(filepath.Join(home, ".ssh"), 0o700); err != nil { t.Fatal(err) } key := filepath.Join(home, ".ssh", "id_ed25519") run(t, "", "ssh-keygen", "-t", "ed25519", "-N", "", "-C", "june", "-f", key, "-q") pub, err := os.ReadFile(key + ".pub") if err != nil { t.Fatal(err) } if _, err := in.DB.CreateAccount(context.Background(), "june", string(pub), false); err != nil { t.Fatal(err) } cmd := exec.Command(binary, "auth", "june", in.URL) cmd.Dir = t.TempDir() cmd.Env = append(os.Environ(), "HOME="+home) raw, err := cmd.CombinedOutput() if err != nil { t.Fatalf("br auth: %v\n%s", err, raw) } out := string(raw) if !strings.Contains(out, "signed in as june") { t.Fatalf("br auth did not sign in: %s", out) } link := "" for _, word := range strings.Fields(out) { if strings.Contains(word, "/auth/claim?c=") { link = word } } if link == "" { t.Fatalf("br auth printed no claim link: %s", out) } client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }} resp, err := client.Get(link) if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusFound || resp.Header.Get("Location") != "/june" { t.Fatalf("the claim link answered %d to %q", resp.StatusCode, resp.Header.Get("Location")) } signed := false for _, c := range resp.Cookies() { if c.Name == "barerepo_session" && c.Value != "" { signed = true } } if !signed { t.Error("the claim link set no session") } again, err := client.Get(link) if err != nil { t.Fatal(err) } again.Body.Close() if again.StatusCode != http.StatusUnauthorized { t.Errorf("the claim link answered %d the second time, want it spent", again.StatusCode) } }