package view
import (
"regexp"
"strings"
"testing"
)
// ---- System log
// The page shows the newest line first, and its one box is the element that
// polls: the fragment answers with that same box.
func TestSystemLogShowsNewestFirstAndPollsItsBox(t *testing.T) {
out := renderSignedIn(t, "system-log", systemLogFixture())
pageHas(t, "System log", out,
`
System log
`, `
Recent lines
`, `Last 200 lines · refreshes on its own`,
`id="system-log" data-poll aria-live="polite" hx-get="/server/log/fragment" hx-trigger="load" hx-swap="outerHTML"`,
`Sep 21 14:01:52 postfix/smtp[2131]`,
`postfix/smtp[2123]`, `postfix/smtpd[2119]`,
)
if strings.Index(out, "postfix/smtp[2131]") > strings.Index(out, "journal-milter[40]") {
t.Error("the log is not shown newest line first")
}
// What a line says is escaped, not interpreted.
if !strings.Contains(out, "to=<m.keller@gmx.de>") || strings.Contains(out, "") {
t.Error("an address in a log line is not escaped")
}
frag := renderFragment(t, "system_log_body", systemLogFixture())
if !strings.HasPrefix(frag, `
`,
)
pageLacks(t, "Backup", out, `notification`)
}
// The box is ticked on arrival, as on the domain export: the secret file is
// sealed unless the operator unticks it, and the page comes back the same way
// after a refusal, with the password fields in view.
func TestBackupEncryptionIsTickedLikeTheDomainExport(t *testing.T) {
backup := renderSignedIn(t, "backup", backupFixture())
export := renderSignedIn(t, "out-domain-settings", outDomainSettingsFixture())
const box = `name="encrypt" value="1" checked data-encrypt-toggle>`
pageHas(t, "Backup", backup, box)
pageHas(t, "Domain settings", export, box)
for _, hook := range []string{`data-encrypt-fields`, `name="password" type="password" autocomplete="new-password" minlength="12" placeholder="at least 12 characters"`,
`name="password_confirm" type="password" autocomplete="new-password"`,
`Keep this password: SelfPost does not store it, and the file cannot be opened without it.`} {
pageHas(t, "Backup", backup, hook)
pageHas(t, "Domain settings", export, hook)
}
}
// A refusal is the flash above the boxes, with the encryption box still ticked.
func TestBackupRefusalIsTheFlashAndKeepsTheTick(t *testing.T) {
out := renderSignedIn(t, "backup", NewBackup(admin(), 12, "The two passwords do not match."))
pageHas(t, "Backup", out, `
`, `The two passwords do not match.`, `name="encrypt" value="1" checked`)
if strings.Index(out, `class="notification`) > strings.Index(out, `class="box"`) {
t.Error("the flash is not above the first box")
}
}
// ---- Users
func TestUsersListsTheReachOfEachUser(t *testing.T) {
out := renderSignedIn(t, "users", usersFixture())
pageHas(t, "Users", out,
`
`,
`mix@example.org`, `team@shop.example.org`, `href="/server/users/3"`,
)
if strings.Count(out, `>you<`) != 1 {
t.Error("only the signed-in user's own row carries the mark")
}
// Without the inbound feature the list has no inbound column, nor a lead
// that speaks of one.
p := NewUsers(admin(), false, "").WithRows([]UserRow{
NewUserRow(UserRowInput{ID: 1, Username: "admin", Global: true, You: true}),
})
out = renderSignedIn(t, "users", p)
pageHas(t, "Users", out, `1 user<`, `
Outbound domains
`)
pageLacks(t, "Users", out, `
Inbound domains
`, `outbound and inbound separately`, `notification`)
// A user who has not set an address is a dash, not a gap.
pageHas(t, "Users", out, `
—
`)
}
func TestReachWords(t *testing.T) {
for _, tt := range []struct {
all bool
names []string
want string
}{
{true, []string{"ignored.example"}, "All"},
{false, []string{"a.example", "b.example"}, "a.example, b.example"},
{false, nil, "—"},
} {
if got := reachWords(tt.all, tt.names); got != tt.want {
t.Errorf("reachWords(%v, %v) = %q, want %q", tt.all, tt.names, got, tt.want)
}
}
// A global user reaches everything whatever the lists say.
row := NewUserRow(UserRowInput{ID: 1, Username: "root", Global: true})
if row.Outbound != "All" || row.Inbound != "All" || row.Role.Status != "global" {
t.Errorf("a global user's row = %+v", row)
}
}
// ---- The user form
// The form for an existing user shows what is stored and sends everything the
// handler reads under the names it has always read it by. Saving it untouched
// sends the same values back: the role as selected, the All ticks as ticked, the
// single domains as ticked, and a blank password, which the handler reads as
// "keep".
func TestUserFormShowsWhatIsStoredUnderTheHandlersFieldNames(t *testing.T) {
out := renderSignedIn(t, "user", userFixture())
pageHas(t, "User", out,
`