Files
mesh-catalog/modules/mesh-mail-web/cmd/mesh-mail-web/mailwrite_test.go
T

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)
}
}
}