package web
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ops"
)
// The page behind a landing email's Accept button: signed out, it sends
// the reader to sign in and back; signed in, it asks one question with
// one button, which arms the landing at the tip the email named. The GET
// writes nothing, because mail scanners open links.
func TestTheApprovePageArmsAtTheEmailedTip(t *testing.T) {
type getIn struct {
Repo string `json:"repo"`
ID string `json:"id"`
}
type row struct {
ID string `json:"id"`
Number int `json:"number"`
Branch string `json:"branch"`
Target string `json:"target"`
Title string `json:"title"`
State string `json:"state"`
}
type getOut struct {
Landing row `json:"landing"`
Unmet []string `json:"unmet"`
CanAccept bool `json:"canAccept"`
}
writes := 0
r := ops.NewRegistry()
if err := ops.Register(r, ops.Op[getIn, getOut]{
Name: "landing.get", Doc: "test stub", Kind: ops.Query, Auth: ops.Anonymous,
Route: "GET /api/v1/r/{repo}/landing/{id}",
Run: func(context.Context, getIn) (getOut, error) {
return getOut{Landing: row{ID: "aaaa1111", Number: 7, Branch: "feature-x", Target: "trunk", Title: "Add x", State: "open"},
Unmet: []string{"ci/quarry.json"}, CanAccept: true}, nil
},
}); err != nil {
t.Fatal(err)
}
if err := ops.Register(r, ops.Op[getIn, struct{}]{
Name: "landing.auto", Doc: "test stub", Kind: ops.Command, Auth: ops.Token,
Route: "POST /api/v1/r/{repo}/landing/{id}/auto",
Run: func(context.Context, getIn) (struct{}, error) { writes++; return struct{}{}, nil },
}); err != nil {
t.Fatal(err)
}
type ver struct {
Version string `json:"version"`
}
if err := ops.Register(r, ops.Op[struct{}, ver]{
Name: "meta.version", Doc: "test stub", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/version",
Run: func(context.Context, struct{}) (ver, error) { return ver{Version: "t"}, nil },
}); err != nil {
t.Fatal(err)
}
verify := Verify(func(repo, user, pass string, need ops.Auth) api.Decision { return api.Allowed })
h := HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}})}, verify)
srv := httptest.NewServer(h)
t.Cleanup(srv.Close)
client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
get := func(signedIn bool, q string) (*http.Response, string) {
req, _ := http.NewRequest("GET", srv.URL+"/r/alpha/landing/aaaa1111/approve"+q, nil)
if signedIn {
req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
b, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return resp, string(b)
}
resp, _ := get(false, "?tip=abcdef012345")
if resp.StatusCode != http.StatusSeeOther || !strings.HasPrefix(resp.Header.Get("Location"), "/login?next=") ||
!strings.Contains(resp.Header.Get("Location"), "approve%3Ftip%3Dabcdef012345") {
t.Fatalf("FAIL: signed out, the page answered %d to %q", resp.StatusCode, resp.Header.Get("Location"))
}
resp, body := get(true, "?tip=abcdef012345")
if resp.StatusCode != http.StatusOK {
t.Fatalf("FAIL: signed in, the page answered %d:\n%s", resp.StatusCode, body)
}
for _, want := range []string{
`data-hook="landing-approve"`,
`action="/r/alpha/landing/aaaa1111/auto"`,
`name="tip" value="abcdef012345"`,
`name="on" value="true"`,
`data-hook="approve-accept"`,
`href="/r/alpha/landing/aaaa1111"`,
} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the approve page lacks %q", want)
}
}
if _, body := get(true, "?tip=not-a-tip"); strings.Contains(body, `data-hook="approve-accept"`) {
t.Error("FAIL: a link naming no tip still offered to accept")
}
if writes != 0 {
t.Errorf("FAIL: opening the page wrote %d time(s)", writes)
}
t.Log("ok: the approve page asks once, signed in, and arms at the emailed tip")
}