package handlers import ( "net/http" "net/url" "strings" "testing" "github.com/mixeme/selfpost/internal/app" "github.com/mixeme/selfpost/internal/store" ) // The pages of the Outbound group, rendered from the real stores and the // handlers, with the refusals each form can come back with. Their markup is // held to the mockups by the guards in internal/web/view; what is checked here // is that the handlers fill the typed pages from what is stored and that every // state a form can end in lands on the page that owns it. func has(t *testing.T, where, body string, wants ...string) { t.Helper() for _, w := range wants { if !strings.Contains(body, w) { t.Errorf("%s is missing %q:\n%s", where, w, body) } } } func lacks(t *testing.T, where, body string, gone ...string) { t.Helper() for _, g := range gone { if strings.Contains(body, g) { t.Errorf("%s carries %q", where, g) } } } // pageOf reads a page of domain 1 as the global administrator, binding the // path's {id} the way the router would. func pageOf(t *testing.T, h http.HandlerFunc, target string) string { t.Helper() rec := send(h, &globalPrincipal, "GET", target, map[string]string{"id": "1"}, nil) if rec.Code != http.StatusOK { t.Fatalf("GET %s = %d, want 200:\n%s", target, rec.Code, rec.Body.String()) } return rec.Body.String() } func TestDomainsListShowsTheAddFormToTheGlobalRoleOnly(t *testing.T) { s := newAppStand(t) body := getBody(t, s.h.HandleDashboard, "/outbound/domains") has(t, "list", body, `

Outbound domains

`, `

Add a domain

`, `action="/outbound/domains"`, `example.org`, `href="/outbound/domains/1/delete"`, `1 domain · DNS cached`, `>DKIM`, `>SPF`, `>DMARC`, `mail`) ops := domainAdmin(t, s.h.store, "ops", s.d.ID) body = getBodyAs(t, s.h.HandleDashboard, "/outbound/domains", ops) has(t, "list for a domain administrator", body, `example.org`, `href="/outbound/domains/1"`) lacks(t, "list for a domain administrator", body, `Add a domain`, `/delete`) // What the viewer does not reach is not listed. other, err := s.h.store.AddDomain("other.example.net", "mail") if err != nil { t.Fatal(err) } lacks(t, "list for a domain administrator", getBodyAs(t, s.h.HandleDashboard, "/outbound/domains", ops), other.Name) has(t, "list", getBody(t, s.h.HandleDashboard, "/outbound/domains"), other.Name) } // A refused name comes back on the list, with what was typed in the field. func TestAddDomainRefusalKeepsWhatWasTyped(t *testing.T) { s := newAppStand(t) rec := postFormAs(s.h.HandleAddDomain, globalPrincipal, "/outbound/domains", nil, url.Values{"name": {`Not A Domain "`}}) if rec.Code != http.StatusBadRequest { t.Fatalf("a bad name = %d, want 400:\n%s", rec.Code, rec.Body.String()) } body := rec.Body.String() has(t, "refused add", body, `
`, `

Add a domain

`, `example.org`) lacks(t, "refused add", body, ``) if domains, _ := s.h.store.ListDomains(); len(domains) != 1 { t.Errorf("a refused name left %d domains", len(domains)) } } func TestDomainPageShowsRecordsApplicationsAndConnection(t *testing.T) { s := newAppStand(t) s.h.cfg.Hostname, s.h.cfg.SubmissionEnabled = "mail.example.org", true a, _, err := s.h.apps.CreateWithSettings(s.d.ID, "prod-server", app.Settings{ Mode: store.AddressModeList, Addresses: []string{"alerts@example.org"}, AuthIPRestrict: true, AuthAllowedIPs: []string{"203.0.113.10", "203.0.113.11"}, Limit: app.Limit{Mode: store.RateLimitModeManual, MaxMessages: 200, WindowSeconds: 3600}, }, nil) if err != nil { t.Fatal(err) } body := pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1") has(t, "domain", body, `

example.org

`, `class="sp-postmark`, `

DKIM

`, `

SPF

`, `

DMARC

`, `mail._domainkey.example.org`, `v=DKIM1`, `_dmarc.example.org`, `v=DMARC1; p=none`, `v=spf1 `, `prod-server`, `alerts@example.org`, `200 / h`, `2 IPs`, `href="/outbound/domains/1/applications/`+idStr(a.ID)+`">Edit`, `mail.example.org`, `STARTTLS (submission)`, `action="/outbound/domains/1/dns-recheck"`, `href="/outbound/log?domain=example.org"`, `href="/outbound/domains/1/settings"`, `href="/outbound/domains/1/delete"`) // The flash the redirects carry. rec := send(s.h.HandleDomainDetail, &globalPrincipal, "GET", "/outbound/domains/1?appsaved=1", s.paths, nil) has(t, "domain after a save", rec.Body.String(), `
`, `Application saved.`) // A domain administrator does not get the global role's delete entry. ops := domainAdmin(t, s.h.store, "ops", s.d.ID) rec = send(s.h.HandleDomainDetail, &ops, "GET", "/outbound/domains/1", s.paths, nil) if rec.Code != http.StatusOK { t.Fatalf("domain page for its administrator = %d", rec.Code) } lacks(t, "domain for a domain administrator", rec.Body.String(), `href="/outbound/domains/1/delete"`) } func TestDomainPageWithoutApplicationsSaysSo(t *testing.T) { s := newAppStand(t) body := pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1") has(t, "domain", body, `No applications yet.`) lacks(t, "domain", body, `Login`) } // The rest of the settings page, beside the report address (the three choices // are in handlers_dmarc_test.go). func TestDomainSettingsPageHasItsBoxes(t *testing.T) { s := newAppStand(t) body := pageOf(t, s.h.HandleDomainSettings, "/outbound/domains/1/settings") has(t, "settings", body, `

Domain settings

`, `action="/outbound/domains/1/settings/reports"`, ``, ``, `action="/outbound/domains/1/settings/ratelimit"`, `action="/outbound/domains/1/settings/export"`, `name="encrypt" value="1" checked`, `minlength="12"`, `

Delete domain

`, `Removes the DKIM key.`) lacks(t, "settings", body, `value="hosted"`, `name="clear"`, `ratelimit/recalc`) } func TestDomainSettingsShowsTheRateLimit(t *testing.T) { s := newAppStand(t) if err := s.h.domains.SaveRateLimit(s.d.ID, store.RateLimit{Mode: store.RateLimitModeManual, MaxMessages: 250, WindowSeconds: 3600}); err != nil { t.Fatal(err) } body := pageOf(t, s.h.HandleDomainSettings, "/outbound/domains/1/settings") has(t, "settings", body, `