Review round 2 F3 (ADR 0517 §3, 1858/AC5): acted=1 stayed in the address, so a reload, a restored tab or a bookmark of it recorded no page opened or opened. An act's 303 (and a mode chosen) now sets mail_acted for 10 seconds, HttpOnly and SameSite=Strict, Secure but on 127.0.0.1; the next list or pane it reaches records nothing and clears it; the address carries no marker. The local run keeps the browser's other cookies.
125 lines
5.2 KiB
Go
125 lines
5.2 KiB
Go
package web
|
|
|
|
// Issue 1859/AC5 (novox/hq ADR 0517 §1, §2, §5): ticking, undoing and reminding an item, and choosing a mode,
|
|
// are posts carrying the session's token; each calls its mail-reading verb and answers the new state; without
|
|
// the token it is refused 403 and nothing is called; a verb that fails is said on the page with its cause.
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// signedIn is a server over b with one live session: its cookie and its form token.
|
|
func signedIn(t *testing.T, b *Bundle) (*Server, *http.Cookie, string) {
|
|
t.Helper()
|
|
sessions := NewSessions(nil)
|
|
id, err := sessions.Create("operator")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sess, _ := sessions.Get(id)
|
|
return NewServer(testHost, &Auth{Sessions: sessions, Pending: NewPendingAuths(nil)}, b),
|
|
&http.Cookie{Name: SessionCookie, Value: id}, sess.Token
|
|
}
|
|
|
|
func post(t *testing.T, h http.Handler, path string, form url.Values, cookie *http.Cookie, fetch bool) (int, string, http.Header) {
|
|
t.Helper()
|
|
r := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode()))
|
|
r.Host = testHost
|
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
if cookie != nil {
|
|
r.AddCookie(cookie)
|
|
}
|
|
if fetch {
|
|
r.Header.Set("X-Requested-With", "fetch")
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
body, _ := io.ReadAll(w.Result().Body)
|
|
return w.Code, string(body), w.Result().Header
|
|
}
|
|
|
|
// 1859/AC5.
|
|
func TestTickingUndoingAndRemindingCallTheirVerbs(t *testing.T) {
|
|
for _, act := range []string{"handle", "unhandle", "remind"} {
|
|
t.Run(act, func(t *testing.T) {
|
|
reading := &readingStandIn{list: fullList()}
|
|
s, cookie, token := signedIn(t, reading.bundle(time.Now()))
|
|
form := url.Values{"id": {"3"}, "item": {"task 1"}, "mode": {"work"}, "act": {act}, FormTokenField: {token}}
|
|
|
|
code, _, h := post(t, s, "/item", form, cookie, false)
|
|
calls := reading.callsOf(act)
|
|
if code != http.StatusSeeOther || h.Get("Location") != "/?mode=work" || len(calls) != 1 ||
|
|
calls[0].Args["id"] != float64(3) || calls[0].Args["item"] != "task 1" || calls[0].Args["mode"] != "work" {
|
|
t.Fatalf("a post of %s: status %d to %q, calls %v; want 303 to /?mode=work and one call", act, code, h.Get("Location"), calls)
|
|
}
|
|
code, body, _ := post(t, s, "/item", form, cookie, true)
|
|
if code != http.StatusOK || !strings.Contains(body, `"done":true`) || len(reading.callsOf(act)) != 2 {
|
|
t.Fatalf("the script's post of %s: status %d %s", act, code, body)
|
|
}
|
|
|
|
form.Del(FormTokenField)
|
|
if code, _, _ := post(t, s, "/item", form, cookie, false); code != http.StatusForbidden || len(reading.callsOf(act)) != 2 {
|
|
t.Fatalf("a post with no token: status %d, %d calls; want 403 and no call", code, len(reading.callsOf(act)))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 1859/AC5: a verb that fails is said on the page as failed, with its cause, both with and without the script.
|
|
func TestAnActThatFailsIsSaidWithItsCause(t *testing.T) {
|
|
reading := &readingStandIn{list: fullList(), fail: map[string]error{"handle": errors.New("the mail entry 3 holds no task 1")}}
|
|
s, cookie, token := signedIn(t, reading.bundle(time.Now()))
|
|
form := url.Values{"id": {"3"}, "item": {"task 1"}, "mode": {"everything"}, "act": {"handle"}, FormTokenField: {token}}
|
|
code, body, _ := post(t, s, "/item", form, cookie, false)
|
|
if code != http.StatusBadGateway || !strings.Contains(body, `class="notice failed"`) || !strings.Contains(body, "holds no task 1") ||
|
|
!strings.Contains(body, `id="today"`) {
|
|
t.Fatalf("a failed tick: status %d, want 502 and the list saying the cause:\n%s", code, body)
|
|
}
|
|
code, body, _ = post(t, s, "/item", form, cookie, true)
|
|
if code != http.StatusBadGateway || !strings.Contains(body, `"failed":`) || !strings.Contains(body, "holds no task 1") {
|
|
t.Fatalf("a failed tick by the script: status %d %s", code, body)
|
|
}
|
|
form.Set("act", "delete")
|
|
if code, _, _ := post(t, s, "/item", form, cookie, false); code != http.StatusBadRequest {
|
|
t.Fatalf("an act that is none of handle, unhandle, remind: status %d, want 400", code)
|
|
}
|
|
}
|
|
|
|
// 1859/AC5, AC2: choosing a mode records it (ADR 0517 §4) and goes to that mode's list.
|
|
func TestChoosingAModeRecordsItAndShowsIt(t *testing.T) {
|
|
reading := &readingStandIn{list: fullList()}
|
|
s, cookie, token := signedIn(t, reading.bundle(time.Now()))
|
|
code, _, h := post(t, s, "/mode", url.Values{"mode": {"personal"}, FormTokenField: {token}}, cookie, false)
|
|
rec := reading.callsOf("record")
|
|
if code != http.StatusSeeOther || h.Get("Location") != "/?mode=personal" || len(rec) != 1 ||
|
|
rec[0].Args["what"] != "mode" || rec[0].Args["mode"] != "personal" {
|
|
t.Fatalf("choosing personal: status %d to %q, records %v", code, h.Get("Location"), rec)
|
|
}
|
|
code, body, _ := post(t, s, "/mode", url.Values{"mode": {"work"}, FormTokenField: {token}}, cookie, true)
|
|
if code != http.StatusOK || !strings.Contains(body, `"go":"/?mode=work"`) {
|
|
t.Fatalf("choosing work by the script: status %d %s", code, body)
|
|
}
|
|
}
|
|
|
|
func newGet(path string, cookie *http.Cookie) *http.Request {
|
|
r := httptest.NewRequest(http.MethodGet, path, nil)
|
|
r.Host = testHost
|
|
if cookie != nil {
|
|
r.AddCookie(cookie)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func serve(h http.Handler, r *http.Request) *httptest.ResponseRecorder {
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w
|
|
}
|