Review round 1 F1 (ADR 0517 §3, §5): a form act went back to the list or the pane, which recorded page opened or opened again, and the script's refresh did too, so last looked became the last act and opened counts grew. The redirect after an act carries acted=1 and the refresh an X-Mail-Refresh header; that page records nothing. The 1860 test now follows each redirect and counts every line, and reminds the task (F2, 1858/AC9), which the store's own integration test also reads back as a date.
125 lines
5.3 KiB
Go
125 lines
5.3 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&acted=1" || 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&acted=1 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&acted=1" || 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\u0026acted=1"`) {
|
|
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
|
|
}
|