Files
selfpost/internal/web/view/dmarc_test.go
T
mixandClaude Opus 5.5 0eaf69a830 dmarc-day, health-sockets: the reporter's day said; the inbound journal dialled
The owner, 2026-10-17: "Показ времени отчёта по UTC принимаем. Это не
проблема. Пояснения добавь. Все мелочи закрой." Designs 9b33b80, 2bf9056.

DMARC: a report's day stays the reporter's, in UTC (RFC 7489 § 7.2); the
report's Window now says so under its times (windowNote), and Settings'
Time zone box and Help (#settings, #dmarc) name it as the one exception to
the panel's zone. formatDMARCWindow counts a window that ends at midnight
as ending the day before, so a daily report is one day, not "20 Sep - 21
Sep" (TestDMARCWindowIsTheReportersDay, in Moscow's zone).

Health: with the inbound relay on, the inbound journal milter's socket is
dialled (InboundJournalSocket through web and handlers Config), fail-open
as the send log's, and Overview's card counts it; the send log's socket is
named journal-milter, as the mockup names it.

The confirmation link's sign-in state is drawn (confirm-email-signin.html);
the panel already renders it.

guide: /license and /server/components, the DMARC pages' paths and day,
selfpost-backup -o / -i / -version, the Inbound log's retention field,
PANEL_COOKIE_SECURE, the inbound journal's socket, the DMARC exception to
the zone. CHANGELOG [Unreleased].

(The e2e case negative_level1_ratelimit, listed as able to pass falsely,
was fixed on 2026-10-14 - the note was stale.)

go vet + go test (26 packages) and make e2e on the test server for this
tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:38:34 +03:00

135 lines
8.0 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package view
import (
"testing"
"time"
)
// ---- The hub
func TestDMARCHubShowsIngestAndTheLatestReports(t *testing.T) {
out := renderSignedIn(t, "dmarc", dmarcHubFixture())
pageHas(t, "DMARC reports", out,
`<h1 class="title is-3">DMARC reports</h1>`, `<code>rua=</code>`,
`<h2>Ingest</h2>`, `<span class="tag is-success is-light">ok</span>`,
`<dd>2026-09-21 06:12</dd>`, `<dd>14 kept · 0 parse failures</dd>`, `<dt>Hosted addresses</dt>`, `<dd class="sp-mono sp-small">dmarc-reports&#43;&lt;domain&gt;@mail.example.org</dd>`, `<dd>500 reports a domain or 90 days</dd>`,
`<th>Received</th>`, `09-21 06:12`, `<a href="/outbound/dmarc/domains/1">example.org</a>`,
"<td>1 903</td>", `<span class="tag is-warning is-light">7</span>`, `<td>0</td><td class="sp-actions"><a href="/outbound/dmarc/reports/41">View</a>`,
)
}
// Before the first report the box says how to get one, and a quiet ingest is
// amber.
func TestDMARCHubWithoutReports(t *testing.T) {
p := NewDMARCHub(admin(), IngestInput{Hosted: "dmarc@mail.example.org", Host: "mail.example.org", RetentionMax: 500, RetentionDays: 90})
out := renderSignedIn(t, "dmarc", p.WithReports(nil))
pageHas(t, "DMARC reports", out, `<span class="tag is-warning is-light">quiet</span>`, `<dd>None yet</dd>`,
`publish MX for <span class="sp-mono">mail.example.org</span> pointing at this server`, `No reports yet.`, `0 kept · 0 parse failures`)
pageLacks(t, "DMARC reports", out, `<table`, `UTC</dd>`)
one := NewDMARCHub(admin(), IngestInput{OK: true, Last: time.Now(), KeptThisWeek: 1, ParseFailures: 1})
pageHas(t, "DMARC reports", renderSignedIn(t, "dmarc", one), `1 kept · 1 parse failure</dd>`)
}
// A report of a domain the viewer cannot open is not a link.
func TestDMARCHubLinksOnlyDomainsThatExist(t *testing.T) {
p := NewDMARCHub(admin(), IngestInput{}).WithReports([]DMARCReportRow{
{Received: "09-21 06:12", Domain: "gone.example", Reporter: "google.com", Window: "20 Sep", Pass: 1, Href: "/outbound/dmarc/reports/1"},
})
out := renderSignedIn(t, "dmarc", p)
pageHas(t, "DMARC reports", out, `<td>gone.example</td>`)
pageLacks(t, "DMARC reports", out, `/outbound/dmarc/domains/`)
}
// ---- One sending domain
// The stamp, the lead and the tables say the same thing about the window: how
// much passed, what to do about what did not.
func TestDMARCDomainStampAndLead(t *testing.T) {
out := renderSignedIn(t, "dmarc-domain", dmarcDomainFixture())
pageHas(t, "DMARC domain", out,
`<h1 class="title is-3">shop.example.org</h1>`, `<a href="/outbound/dmarc">DMARC reports</a> /`,
`class="sp-postmark sp-warn"`, `<span>DMARC</span><b>99.6%</b><span>PASS · 7 DAYS</span>`,
"11 204 pass · 41 fail in the last 7 days. Failures come from a source that is not this relay — review before tightening <code>p=</code>.",
`<a class="button" href="/outbound/domains/2"><i class="ti ti-world-upload"></i>Domain page</a>`,
`<h2>Sources · 7 days</h2>`, `203.0.113.25 <span class="tag is-light">this relay</span>`,
`<td>0</td><td>41</td><td><span class="tag is-warning is-light">none</span></td>`,
`<td><span class="tag is-success is-light">none</span></td>`, `<h2>Reports</h2>`, `<td class="sp-muted">Outlook.com</td>`,
)
for _, c := range []struct {
pass, fail int
level string
word string
}{
{500, 0, LevelOK, "100%"}, {9999, 1, LevelWarn, "99.9%"}, {1, 99999, LevelWarn, "0.0%"}, {0, 5, LevelFail, "0.0%"},
} {
p := NewDMARCDomain(admin(), 1, "example.org", true, c.pass, c.fail, 7, "")
if m := p.Head.Postmark; m == nil || m.Level != c.level || m.Word != c.word {
t.Errorf("%d pass, %d fail are stamped %+v, want %s %s", c.pass, c.fail, m, c.level, c.word)
}
}
// Rounding never turns a failure into a clean 100%.
if got := passRate(99999, 1); got != "99.9%" {
t.Errorf("99999 of 100000 is stamped %s", got)
}
}
// With nothing in the window there is nothing to stamp; and a viewer who does not
// reach the hub is not offered the way back to it.
func TestDMARCDomainWithoutMailOrWithoutHub(t *testing.T) {
p := NewDMARCDomain(Meta{User: "ops", HasOutbound: true}, 1, "example.org", false, 0, 0, 7, "No messages in the reporting window yet.")
out := renderSignedIn(t, "dmarc-domain", p.WithSources(nil).WithReports(nil))
pageHas(t, "DMARC domain", out, `<p class="sp-kicker">DMARC reports /</p>`, `0 pass · 0 fail in the last 7 days. No messages in the reporting window yet.`,
`No mail from any source in this window.`, `No reports for this domain yet.`)
pageLacks(t, "DMARC domain", out, `sp-postmark`, `<table`, `<a href="/outbound/dmarc">`)
}
func TestWithCodeOnlyMarksP(t *testing.T) {
out := renderSignedIn(t, "dmarc-domain", NewDMARCDomain(admin(), 1, "example.org", true, 1, 0, 7, "Raise p= to quarantine, then p=reject.").WithSources(nil).WithReports(nil))
pageHas(t, "DMARC domain", out, `Raise <code>p=</code> to quarantine, then <code>p=</code>reject.`)
}
// ---- One report
func TestDMARCReportShowsTheReportItsPolicyAndRecords(t *testing.T) {
out := renderSignedIn(t, "dmarc-report", dmarcReportFixture())
pageHas(t, "DMARC report", out,
`<h1 class="title is-4">Outlook.com · 20 Sep</h1>`,
`<a href="/outbound/dmarc">DMARC reports</a> / <a href="/outbound/dmarc/domains/2">shop.example.org</a> /`,
"<span>shop.example.org</span><span class=\"tag is-success is-light\">1 903 pass</span><span class=\"tag is-warning is-light\">7 fail</span>",
`<h2>Report</h2>`, `<dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd>`, `<dd>20 Sep 00:00 – 21 Sep 00:00</dd><dd class="sp-muted">The reporter&#39;s day, 20 Sep: reports count their days in UTC, and the title and the lists name that day. The times are this panel&#39;s zone.</dd>`, `<dd>2026-09-21 04:40</dd>`,
`<h2>Published policy</h2>`, `<dd class="sp-mono">none / none / 100</dd>`, `<dd class="sp-mono">r / r</dd>`, `<dd class="sp-mono sp-small">dmarc@mail.example.org</dd>`,
`<h2>Records</h2>`, `Parsed from the aggregate XML`,
`203.0.113.25 <span class="tag is-light">this relay</span>`, "<td>1 903</td>",
`<td><span class="tag is-success is-light">pass</span></td><td><span class="tag is-success is-light">pass</span></td>`,
`<td><span class="tag is-warning is-light">none</span></td><td><span class="tag is-danger is-light">fail</span></td><td><span class="tag is-danger is-light">fail</span></td>`,
)
}
// What a report does not hold is not drawn: no contact, no rua, no fail tag when
// nothing failed, and a report with no records says so.
func TestDMARCReportOmitsWhatItDoesNotHold(t *testing.T) {
p := NewDMARCReport(admin(), ReportInput{ID: 1, Reporter: "google.com", ReportID: "r1", Domain: "example.org", DomainID: 1, Pass: 5,
Period: "20 Sep 00:00 – 20 Sep 23:59", Received: "2026-09-21 04:40", PolicyP: "none", PolicySP: "none", PolicyPct: 100, PolicyADKIM: "r", PolicyASPF: "r"})
out := renderSignedIn(t, "dmarc-report", p.WithRecords(nil))
pageHas(t, "DMARC report", out, `<h1 class="title is-4">google.com</h1>`, `<span class="tag is-success is-light">5 pass</span>`, `This report holds no records.`)
pageLacks(t, "DMARC report", out, `fail</span>`, `Contact`, `<dt>rua</dt>`, `<table`, `<a href="/outbound/dmarc">`)
}
func TestDMARCPagesEscapeWhatReportersWrote(t *testing.T) {
evil := `<script>alert(1)</script>`
hub := NewDMARCHub(admin(), IngestInput{Hosted: evil}).WithReports([]DMARCReportRow{{Domain: evil, Reporter: evil, Window: evil, Received: evil, Href: "/x"}})
dom := NewDMARCDomain(admin(), 1, evil, true, 1, 1, 7, evil).
WithSources([]DMARCSourceRow{{Source: evil, Disposition: evil}}).WithReports([]DMARCReportRow{{Reporter: evil, Href: "/x"}})
rep := NewDMARCReport(admin(), ReportInput{Reporter: evil, ReportID: evil, Domain: evil, Contact: evil, Period: evil, Received: evil,
PolicyP: evil, PolicyADKIM: evil, Recipient: evil}).
WithRecords([]DMARCRecordRow{{Source: evil, Disposition: evil, SPF: evil, DKIM: evil, HeaderFrom: evil}})
for name, c := range map[string]struct {
page string
data any
}{"hub": {"dmarc", hub}, "domain": {"dmarc-domain", dom}, "report": {"dmarc-report", rep}} {
pageLacks(t, name, renderSignedIn(t, c.page, c.data), `<script>alert`)
}
}