issue 1859: move the mail page into internal/web, so the local run can serve it
A program cannot import a main package; the page's code moves to modules/mesh-mail-web/internal/web, and cmd/mesh-mail-web is a two-line program calling it, so cmd/mail-page-dev (1859.5) serves the same handlers. Nothing the module does changes.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package web
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user