test / test (push) Canceled after 0s
The pages of the mockups accepted in 87fa0a4, built from the kit's partials
(written by a Sonnet agent into the working tree, reviewed here):
- Account: "Send me notifications" in the Profile form, saved with the
profile; an unticked box is off.
- Server > Settings: the instance switch (box 03), and box 04 Notification
mail with the DKIM and SPF records of the server's own name, drawn with the
record partial of a domain's DNS page and checked in DNS. Sending rate
limits are box 02 in the left column. A record that is not in place is
tagged as a warning, not a failure: notifications are sent without it.
- Server > Health: card 06 Notification mail with the state of each record
and a link to Settings, beside Configuration (07); part of the polled
fragment; Re-check DNS forces it too. Overview does not run this check.
- help.go: the Notifications topic and the clauses in Settings and Your
account, word for word with help.html.
If the server's key cannot be read the pages still render: the DKIM record
is left out and SPF is shown unchecked.
e2e: the administrator gets a mailbox behind the sink and the server's DKIM
record from Settings is published; the level-2 refusal of the next step must
then arrive as a notification from postmaster@mail.e2e.test, signed as that
name with the key Settings shows, and must not be a row of the Outbound log.
The rotation step now waits for Postfix to be running: it follows a step that
restarts the container, and logrotate's `postfix reload` failed in between.
On the test server, for this tree: go vet clean, go test ./... 23 packages
ok, make e2e ok (147 s); the notification reached the sink 28 s after the
refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
package e2e
|
|
|
|
import (
|
|
"fmt"
|
|
"html"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var sendLogDetailsRe = regexp.MustCompile(`href="(/outbound/log/\d+[^"]*)"`)
|
|
|
|
// firstDeliveryPage returns the address of the page of the newest row in the
|
|
// sender domain's send log.
|
|
func firstDeliveryPage(sc *scenario) (string, error) {
|
|
rows, err := sc.panel.sendLogRows(senderDomain, "")
|
|
if err != nil {
|
|
return "", fmt.Errorf("read the send log: %w", err)
|
|
}
|
|
m := sendLogDetailsRe.FindStringSubmatch(rows)
|
|
if m == nil {
|
|
return "", fmt.Errorf("no Details link in the send log:\n%s", rows)
|
|
}
|
|
return html.UnescapeString(m[1]), nil
|
|
}
|
|
|
|
// testDeliveryLogSurvivesRotation checks that a message's page still lists
|
|
// what Postfix wrote about it after mail.log has been rotated: the lines are
|
|
// kept in the database by the log tailer, not searched for in the current
|
|
// file (architecture.md § Log tailer, Stored lines). The message is the first
|
|
// one delivered, whose page send_verify_dkim_and_status noted.
|
|
func testDeliveryLogSurvivesRotation(t *testing.T, sc *scenario) {
|
|
page := sc.deliveryPage
|
|
|
|
// What the page has to show: the line that opened the queue id and the
|
|
// delivery line. The tailer has read both - the row is "sent" - but it
|
|
// writes them one at a time, so the page is polled rather than read once.
|
|
wanted := []string{"client=", "status=sent"}
|
|
shows := func() (bool, error) {
|
|
_, body, err := sc.panel.get(page)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, w := range wanted {
|
|
if !strings.Contains(body, w) {
|
|
return false, fmt.Errorf("%s does not show a %q line", page, w)
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
if err := waitFor("the message's page to list its log lines", 15*time.Second, 500*time.Millisecond, shows); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The rotation ends with `postfix reload`, which fails while Postfix is
|
|
// still starting - and this step follows one that restarts the container.
|
|
if err := waitFor("Postfix to be running", 30*time.Second, 500*time.Millisecond, func() (bool, error) {
|
|
if _, err := h.execIn("selfpost", "postfix", "status"); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := h.execIn("selfpost", "logrotate", "-f", "/etc/logrotate.d/mail"); err != nil {
|
|
t.Fatalf("logrotate -f: %v", err)
|
|
}
|
|
// The current file must no longer hold the delivery line, or the page
|
|
// finding it there would prove nothing. grep exits 1 on no match.
|
|
if out, err := h.execIn("selfpost", "sh", "-c", "grep -c 'status=sent' /data/log/mail.log || true"); err != nil || strings.TrimSpace(out) != "0" {
|
|
t.Fatalf("mail.log still holds delivery lines after the rotation (count=%q err=%v)", strings.TrimSpace(out), err)
|
|
}
|
|
|
|
if ok, err := shows(); !ok {
|
|
t.Fatalf("after mail.log was rotated: %v", err)
|
|
}
|
|
}
|