169 lines
6.0 KiB
Go
169 lines
6.0 KiB
Go
package main
|
|
|
|
// Corrections and undo on the mail page (novox/hq ADR 0459 §8, §13; issue 1523).
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// 1523/AC1: posting a correction with the session's own token calls mail-reading.correct with the mail's id
|
|
// and the change, then shows the page again.
|
|
func TestMailCorrectHandlerCallsCorrectAndShowsThePageAgain(t *testing.T) {
|
|
sessions := NewSessions(nil)
|
|
sess, cookie := sessionAndCookie(t, sessions, "alice")
|
|
auth := &Auth{Sessions: sessions}
|
|
|
|
accountsRaw, err := accountsAnswerJSON()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var gotArgs map[string]any
|
|
b := &Bundle{
|
|
Accounts: func(context.Context) (json.RawMessage, error) { return accountsRaw, nil },
|
|
MailStatus: seatAskJSON(t, map[string]any{"healthy": true}),
|
|
MailEntries: seatAskJSON(t, map[string]any{"entries": []map[string]any{}}),
|
|
MailGroups: seatAskJSON(t, map[string]any{"groups": []map[string]any{}}),
|
|
MailLessons: seatAskJSON(t, map[string]any{"lessons": []map[string]any{}}),
|
|
MailCorrect: func(_ context.Context, args map[string]any) (json.RawMessage, error) {
|
|
gotArgs = args
|
|
return json.Marshal(map[string]any{"id": args["id"]})
|
|
},
|
|
}
|
|
handler := auth.RequireSession(RequireFormToken(b.mailCorrectHandler))
|
|
|
|
form := url.Values{}
|
|
form.Set("id", "42")
|
|
form.Set("mark", "matters")
|
|
form.Set(FormTokenField, sess.Token)
|
|
r := httptest.NewRequest(http.MethodPost, "/mail-correct", strings.NewReader(form.Encode()))
|
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
r.AddCookie(cookie)
|
|
w := httptest.NewRecorder()
|
|
handler(w, r)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want 200: %s", w.Code, w.Body.String())
|
|
}
|
|
if gotArgs["id"] != float64(42) || gotArgs["mark"] != "matters" {
|
|
t.Fatalf("mail-reading.correct asked with %v", gotArgs)
|
|
}
|
|
if !strings.Contains(w.Body.String(), "<h1>Accounts</h1>") {
|
|
t.Fatalf("the page was not shown again: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// 1523/AC1: without the token, the POST is answered 403 and mail-reading.correct is never called.
|
|
func TestMailCorrectHandlerRefusedWithoutToken(t *testing.T) {
|
|
sessions := NewSessions(nil)
|
|
_, cookie := sessionAndCookie(t, sessions, "alice")
|
|
auth := &Auth{Sessions: sessions}
|
|
called := false
|
|
b := &Bundle{MailCorrect: func(context.Context, map[string]any) (json.RawMessage, error) {
|
|
called = true
|
|
return nil, nil
|
|
}}
|
|
handler := auth.RequireSession(RequireFormToken(b.mailCorrectHandler))
|
|
|
|
form := url.Values{}
|
|
form.Set("id", "42")
|
|
r := httptest.NewRequest(http.MethodPost, "/mail-correct", strings.NewReader(form.Encode()))
|
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
r.AddCookie(cookie)
|
|
w := httptest.NewRecorder()
|
|
handler(w, r)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Fatalf("status %d, want 403", w.Code)
|
|
}
|
|
if called {
|
|
t.Fatal("mail-reading.correct was called with no token")
|
|
}
|
|
}
|
|
|
|
// 1523/AC2: an undo form carrying the session's own token calls mail-reading.undo.
|
|
func TestMailUndoHandlerCallsUndo(t *testing.T) {
|
|
sessions := NewSessions(nil)
|
|
sess, cookie := sessionAndCookie(t, sessions, "alice")
|
|
auth := &Auth{Sessions: sessions}
|
|
|
|
accountsRaw, err := accountsAnswerJSON()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var gotArgs map[string]any
|
|
b := &Bundle{
|
|
Accounts: func(context.Context) (json.RawMessage, error) { return accountsRaw, nil },
|
|
MailStatus: seatAskJSON(t, map[string]any{"healthy": true}),
|
|
MailEntries: seatAskJSON(t, map[string]any{"entries": []map[string]any{}}),
|
|
MailGroups: seatAskJSON(t, map[string]any{"groups": []map[string]any{}}),
|
|
MailLessons: seatAskJSON(t, map[string]any{"lessons": []map[string]any{}}),
|
|
MailUndo: func(_ context.Context, args map[string]any) (json.RawMessage, error) {
|
|
gotArgs = args
|
|
return json.Marshal(map[string]any{"undone": args["id"]})
|
|
},
|
|
}
|
|
handler := auth.RequireSession(RequireFormToken(b.mailUndoHandler))
|
|
|
|
form := url.Values{}
|
|
form.Set("id", "7")
|
|
form.Set(FormTokenField, sess.Token)
|
|
r := httptest.NewRequest(http.MethodPost, "/mail-undo", strings.NewReader(form.Encode()))
|
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
r.AddCookie(cookie)
|
|
w := httptest.NewRecorder()
|
|
handler(w, r)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want 200: %s", w.Code, w.Body.String())
|
|
}
|
|
if gotArgs["id"] != float64(7) {
|
|
t.Fatalf("mail-reading.undo asked with %v", gotArgs)
|
|
}
|
|
}
|
|
|
|
// 1523/AC2, AC3: the page lists the reading lessons newest first, each with an undo form carrying the
|
|
// session's token, and shows reading lessons per 100 read mails for each of the last weeks, from status.
|
|
func TestAccountsPageListsLessonsWithUndoFormsAndWeeklyMeasure(t *testing.T) {
|
|
sessions := NewSessions(nil)
|
|
sess, cookie := sessionAndCookie(t, sessions, "alice")
|
|
auth := &Auth{Sessions: sessions}
|
|
|
|
accountsRaw, err := accountsAnswerJSON()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := &Bundle{
|
|
Accounts: func(context.Context) (json.RawMessage, error) { return accountsRaw, nil },
|
|
MailStatus: seatAskJSON(t, map[string]any{
|
|
"healthy": true,
|
|
"weeks": []map[string]any{
|
|
{"week-start": "2026-10-13", "lessons-per-100": 25.0},
|
|
{"week-start": "2026-10-20", "lessons-per-100": nil, "why": "no mail was read this week"},
|
|
},
|
|
}),
|
|
MailEntries: seatAskJSON(t, map[string]any{"entries": []map[string]any{}}),
|
|
MailGroups: seatAskJSON(t, map[string]any{"groups": []map[string]any{}}),
|
|
MailLessons: seatAskJSON(t, map[string]any{"lessons": []map[string]any{
|
|
{"id": 5, "sender-name": "Alice", "subject": "pay the bill", "change": "mark changed", "made": "2026-10-20T10:00:00Z"},
|
|
}}),
|
|
}
|
|
handler := auth.RequireSession(b.accountsPage)
|
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
r.AddCookie(cookie)
|
|
w := httptest.NewRecorder()
|
|
handler(w, r)
|
|
body := w.Body.String()
|
|
|
|
for _, want := range []string{"Alice", "pay the bill", "mark changed", `action="/mail-undo"`, sess.Token, "25 per 100", "no mail was read this week"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("body does not contain %q:\n%s", want, body)
|
|
}
|
|
}
|
|
}
|