package e2e import ( "io" "net/http" "os/exec" "strings" "testing" bartoken "github.com/barerepo/server/internal/token" ) // readAll drains a response body and returns it as a string. func readAll(t *testing.T, resp *http.Response) string { t.Helper() body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } return string(body) } // Chapter 24: three commands, all visible at once, token inside. A button to reveal them is a step. func TestAddingARunnerIsOnePageAndNoClicks(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") token, err := in.db.NewSession(t.Context(), "john") if err != nil { t.Fatal(err) } req, err := http.NewRequest(http.MethodGet, in.http.URL+"/john/johnbot/runners/new", nil) if err != nil { t.Fatal(err) } req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: token}) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() body := readAll(t, resp) if strings.Contains(body, "new runner token") { t.Error("the page asks for a click before it shows the commands, which chapter 24 forbids") } for _, want := range []string{"runner.sh", "runner.ps1", "barerepo-runner ", "rt_live_"} { if !strings.Contains(body, want) { t.Errorf("the page is missing %q:\n%s", want, body) } } // The three facts chapter 24 asks for, in the mockup's words. for _, want := range []string{"dials out", "scopes to this repo", "shown once"} { if !strings.Contains(body, want) { t.Errorf("the page does not say %q", want) } } // A reload must not revoke the line the reader just copied. first := tokenIn(t, body) req2, _ := http.NewRequest(http.MethodGet, in.http.URL+"/john/johnbot/runners/new", nil) req2.AddCookie(&http.Cookie{Name: "barerepo_session", Value: token}) resp2, err := http.DefaultClient.Do(req2) if err != nil { t.Fatal(err) } defer resp2.Body.Close() readAll(t, resp2) if _, err := in.db.AccountForToken(t.Context(), bartoken.Runner, first); err != nil { t.Errorf("reloading the page revoked the token the reader had already copied: %v", err) } } func tokenIn(t *testing.T, body string) string { t.Helper() i := strings.Index(body, "rt_live_") if i < 0 { t.Fatal("no runner token on the page") } rest := body[i:] end := strings.IndexAny(rest, "< \n") if end < 0 { t.Fatal("the token does not end") } return rest[:end] } // Chapter 24 puts a description on the new repository form, and only the pasted block can store it. func TestTheNewRepositoryDescriptionReachesThePasteBlock(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) in.account("john") session, err := in.db.NewSession(t.Context(), "john") if err != nil { t.Fatal(err) } form := "name=johnbot&description=irc+bot+that+refuses+to+leave&visibility=public&default_branch=master" req, err := http.NewRequest(http.MethodPost, in.http.URL+"/new", strings.NewReader(form)) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: session}) 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() if resp.StatusCode != http.StatusFound { t.Fatalf("creating answered %d", resp.StatusCode) } // The repository is private until the config is pushed, so the empty page needs the session. page, err := http.NewRequest(http.MethodGet, in.http.URL+resp.Header.Get("Location"), nil) if err != nil { t.Fatal(err) } page.AddCookie(&http.Cookie{Name: "barerepo_session", Value: session}) shown, err := http.DefaultClient.Do(page) if err != nil { t.Fatal(err) } defer shown.Body.Close() if shown.StatusCode != http.StatusOK { t.Fatalf("the empty page answered %d", shown.StatusCode) } body := readAll(t, shown) if !strings.Contains(body, `description = "irc bot that refuses to leave"`) { t.Errorf("the description is not in the block the reader pastes:\n%s", body) } // A quoted heredoc passes every character through, which printf with escapes does not. if !strings.Contains(body, "<<'EOF'") { t.Errorf("the block is not a quoted heredoc, so a quote in the description would break it") } if !strings.Contains(body, `visibility = "public"`) { t.Errorf("the block does not carry the visibility the form was told:\n%s", body) } } // The block writes the file, so it has to write the visibility that was asked for and not a default. func TestADescribedPrivateRepositoryStaysPrivateInThePasteBlock(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) in.account("john") session, err := in.db.NewSession(t.Context(), "john") if err != nil { t.Fatal(err) } form := "name=johnbot&description=irc+bot&visibility=private&default_branch=master" req, err := http.NewRequest(http.MethodPost, in.http.URL+"/new", strings.NewReader(form)) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: session}) 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() page, err := http.NewRequest(http.MethodGet, in.http.URL+resp.Header.Get("Location"), nil) if err != nil { t.Fatal(err) } page.AddCookie(&http.Cookie{Name: "barerepo_session", Value: session}) shown, err := http.DefaultClient.Do(page) if err != nil { t.Fatal(err) } defer shown.Body.Close() body := readAll(t, shown) if !strings.Contains(body, `visibility = "private"`) { t.Errorf("a repository asked to be private is told to write public:\n%s", body) } if strings.Contains(body, `visibility = "public"`) { t.Errorf("the block writes public for a repository that asked to be private:\n%s", body) } }