package e2e import ( "context" "net/http" "net/url" "os/exec" "strings" "testing" ) // post sends a form as one account, without following the redirect, so the answer can be read. func post(t *testing.T, in *instance, account, path string, form url.Values) *http.Response { t.Helper() body := strings.NewReader(form.Encode()) req, err := http.NewRequest(http.MethodPost, in.http.URL+path, body) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if account != "" { token, err := in.db.NewSession(context.Background(), account) if err != nil { t.Fatal(err) } req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: token}) } client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }} resp, err := client.Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp } // Chapter 21.1. Copying is the answer to "no forks", so a reader who is not the owner performs it. func TestAReaderCopiesARepositoryFromTheConfigPage(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") in.account("lisa") seed(t, in, john, "john", "johnbot") // A proposal ref exists on the original, and chapter 21.1 says it must not come across. work := clone(t, in, john, "/john/johnbot") commit(t, work, "package main\n\nfunc main() {}\n", "a proposal") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "HEAD:refs/proposals/new") // Guard the guard: if the original has no proposal ref, the assertion below proves nothing. if refs := run(t, repoDir(t, in, "john", "johnbot"), "git", "for-each-ref", "--format=%(refname)"); !strings.Contains(refs, "refs/proposals/") { t.Fatalf("the push made no proposal ref, so this test cannot show one is left behind:\n%s", refs) } resp := post(t, in, "lisa", "/john/johnbot/copy", nil) if resp.StatusCode != http.StatusFound { t.Fatalf("the copy answered %d, wanted a redirect to the new repository", resp.StatusCode) } if got := resp.Header.Get("Location"); got != "/lisa/johnbot" { t.Errorf("the copy sent the reader to %q", got) } if code, _, _ := get(t, in.http.URL+"/lisa/johnbot"); code != http.StatusOK { t.Fatalf("the copy is not being served: %d", code) } dir := repoDir(t, in, "lisa", "johnbot") refs := run(t, dir, "git", "for-each-ref", "--format=%(refname)") if !strings.Contains(refs, "refs/heads/master") { t.Errorf("the copy has no branches:\n%s", refs) } if strings.Contains(refs, "refs/proposals/") { t.Errorf("proposal refs came across, and chapter 21.1 says they belong to the original:\n%s", refs) } // Chapter 21.1 copies threads and notes, which is the half a fast object copy does not bring. if !strings.Contains(refs, "refs/notes/threads/") { t.Errorf("the discussion did not come across, so the copy lost the conversation:\n%s", refs) } if _, _, page := get(t, in.http.URL+"/lisa/johnbot/threads"); !strings.Contains(page, "1") { t.Errorf("the copy's thread list is empty:\n%s", page) } // A copy borrows the original's objects, so deleting the original must give them to the copy first. post(t, in, "john", "/john/johnbot/delete", url.Values{"confirm": {"johnbot"}}) if code, _, _ := get(t, in.http.URL+"/lisa/johnbot"); code != http.StatusOK { t.Fatalf("deleting the original took the copy's history with it: %d", code) } if out := run(t, dir, "git", "log", "--format=%s", "master"); !strings.Contains(out, "first") { t.Errorf("the copy lost its history when the original went:\n%s", out) } } // A signed-out reader is sent to sign in, because a copy needs an account to own it. func TestCopyNeedsAnAccount(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") seed(t, in, john, "john", "johnbot") resp := post(t, in, "", "/john/johnbot/copy", nil) if got := resp.Header.Get("Location"); got != "/signin" { t.Errorf("an anonymous copy went to %q, wanted /signin", got) } }