fix(account): do not claim the invite was emailed when mail is off
The create notice said "emailed the invitation" even when the mailer is the no-op fallback (no SMTP host). It now reports that email is unconfigured and points at the shown link — the copy/paste fallback the spec calls for — and only claims a send when a real mailer is wired. Covered by a test.
This commit is contained in:
parent
13133717e1
commit
3a258aeb55
2 changed files with 47 additions and 5 deletions
|
|
@ -222,8 +222,17 @@ func (a *Actions) Create(w http.ResponseWriter, r *http.Request, rc *server.Requ
|
|||
}
|
||||
link := a.inviteLink(guid)
|
||||
a.sendInvite(ctx, name, email, link)
|
||||
return a.renderList(w, r, rc, http.StatusOK,
|
||||
fmt.Sprintf("Created %s and emailed the invitation.", name), "notice", link)
|
||||
return a.renderList(w, r, rc, http.StatusOK, a.inviteNotice(name), "notice", link)
|
||||
}
|
||||
|
||||
// inviteNotice is the outcome line after a successful create. It does not claim
|
||||
// an email was sent when mail is not configured — the link below is the
|
||||
// fallback the admin shares by hand (SPEC-010 §5).
|
||||
func (a *Actions) inviteNotice(name string) string {
|
||||
if _, nop := a.d.Mailer.(mail.Nop); nop || a.d.Mailer == nil {
|
||||
return fmt.Sprintf("Created %s. Email is not configured — share the invitation link below.", name)
|
||||
}
|
||||
return fmt.Sprintf("Created %s and emailed the invitation.", name)
|
||||
}
|
||||
|
||||
// createError maps a users.Create sentinel to an HTTP status + a human message.
|
||||
|
|
|
|||
|
|
@ -34,12 +34,19 @@ func spec010Gate() *server.Tiers {
|
|||
// ceremony the account page reuses) and the admin-only role gate. It returns
|
||||
// the harness and the recording mailer.
|
||||
func spec010Door(t *testing.T) (*harness, *mail.Fake) {
|
||||
t.Helper()
|
||||
mailer := mail.NewFake()
|
||||
return spec010DoorWith(t, mailer), mailer
|
||||
}
|
||||
|
||||
// spec010DoorWith is spec010Door with an explicit mailer (a test can pass
|
||||
// mail.Nop{} to exercise the "email is not configured" path).
|
||||
func spec010DoorWith(t *testing.T, mailer mail.Mailer) *harness {
|
||||
t.Helper()
|
||||
mods, err := module.Load(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("module.Load: %v", err)
|
||||
}
|
||||
mailer := mail.NewFake()
|
||||
|
||||
h := newHarness(t, func(d *server.Deps) {
|
||||
reg := server.NewMapRegistry()
|
||||
|
|
@ -62,7 +69,7 @@ func spec010Door(t *testing.T) (*harness, *mail.Fake) {
|
|||
})
|
||||
web.SetNav(mods)
|
||||
t.Cleanup(func() { web.SetNav(nil) })
|
||||
return h, mailer
|
||||
return h
|
||||
}
|
||||
|
||||
// spec010Team creates an ACTIVE team member under the admin (for the non-admin
|
||||
|
|
@ -245,8 +252,32 @@ func TestSpec010AC3_CannotCreateARoleAtOrAboveYourOwn(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── AC-4 — the invite path enrols ────────────────────────────────────────────
|
||||
// TestSpec010AC3_NoMailStillShowsTheLink: with mail unconfigured the invite is
|
||||
// not claimed to be emailed — the link is the fallback (SPEC-010 §5).
|
||||
func TestSpec010AC3_NoMailStillShowsTheLink(t *testing.T) {
|
||||
h := spec010DoorWith(t, mail.Nop{})
|
||||
|
||||
body := createUser(t, h, "nop@example.com", "team")
|
||||
u, err := h.users.ByEmail(h.ctx, "nop@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("ByEmail: %v", err)
|
||||
}
|
||||
idn, err := h.ids.ByUserID(h.ctx, u.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ByUserID: %v", err)
|
||||
}
|
||||
if !strings.Contains(body, "/"+idn.GUID) {
|
||||
t.Errorf("the invite link is not shown when mail is unconfigured:\n%s", body)
|
||||
}
|
||||
if strings.Contains(body, "emailed the invitation") {
|
||||
t.Errorf("the page claims an email was sent with no mailer configured:\n%s", body)
|
||||
}
|
||||
if !strings.Contains(body, "not configured") {
|
||||
t.Errorf("the page does not say mail is unconfigured:\n%s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// ── the account page's factors ───────────────────────────────────────────────
|
||||
// TestSpec010_AccountShowsTheUsersFactors: the account page reports the passkey
|
||||
// count and whether TOTP is enrolled (SPEC-010 §4).
|
||||
func TestSpec010_AccountShowsTheUsersFactors(t *testing.T) {
|
||||
|
|
@ -275,6 +306,8 @@ func TestSpec010_AccountShowsTheUsersFactors(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── AC-4 — the invite path enrols ────────────────────────────────────────────
|
||||
|
||||
// TestSpec010AC4_InvitedUserEnrolsAtTheGUID: the emailed link opens the
|
||||
// enrolment screen (SPEC-006's invite path — no new flow).
|
||||
func TestSpec010AC4_InvitedUserEnrolsAtTheGUID(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue