`,
)
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,
`
Users
`, `
01Panel users
`, `3 users`,
`href="/server/users/new"`, `Create user`, `
`, `User created.`,
`
admin you`,
`
global`, `
domain`,
`
Outbound domains | Inbound domains | `,
`
shop.example.org | — | `,
`
All | acme.io | `,
`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,
`acme-ops
`, `href="/server/users">Users`, `href="/server/users/3/delete"`,
`