package mail_test import ( "context" "errors" "strings" "testing" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib/access" "atlas9.dev/c/demo/lib/mail" mailtransport "atlas9.dev/c/mail" ) // spyTransport records the last message it was handed, and whether it was // reached at all. type spyTransport struct { sent bool email string content mailtransport.Content } func (s *spyTransport) Send(ctx context.Context, addr string, c mailtransport.Content) error { s.sent = true s.email = addr s.content = c return nil } // Without CapMailSend the egress boundary refuses the message and the transport // is never reached: the cap check is the chokepoint, not the transport. func TestMailer_DeniedWithoutCap(t *testing.T) { spy := &spyTransport{} m := &mail.Mailer{Transport: spy, Guard: access.ContextGuard{}, BaseURL: "https://x"} ctx := access.Put(context.Background(), access.Access{}) err := m.SendVerification(ctx, "to@test.com", "tok") if !errors.Is(err, iam.ErrForbidden) { t.Fatalf("want ErrForbidden, got %v", err) } if spy.sent { t.Fatal("transport was reached without the cap") } } // With CapMailSend the mailer composes the message and delegates to the // transport. func TestMailer_ComposesAndSendsWithCap(t *testing.T) { spy := &spyTransport{} m := &mail.Mailer{Transport: spy, Guard: access.ContextGuard{}, BaseURL: "https://x"} ctx := access.Put(context.Background(), access.Access{}.WithSystem(mail.CapMailSend)) err := m.SendVerification(ctx, "to@test.com", "tok") if err != nil { t.Fatalf("send: %v", err) } if !spy.sent { t.Fatal("transport was not reached") } if spy.email != "to@test.com" { t.Fatalf("wrong recipient: %q", spy.email) } // Composition is the mailer's job: the verification link carries the token // and points at BaseURL. if !strings.Contains(spy.content.HtmlBody, "https://x/verify?token=tok") { t.Fatalf("verification link missing from body: %q", spy.content.HtmlBody) } }