package email_test import ( "strings" "testing" emailpkg "git.threesix.ai/jordan/persona-community-2/pkg/email" "git.threesix.ai/jordan/persona-community-2/services/persona-api/internal/email" ) var testBrand = emailpkg.BrandConfig{ AppName: "Test App", AppURL: "https://example.com", SupportEmail: "support@example.com", PrimaryColor: "#6366f1", } func newTestRenderer(t *testing.T) *emailpkg.Renderer { t.Helper() r, err := emailpkg.NewRendererFromFS(email.TemplateFS, "templates", testBrand) if err != nil { t.Fatalf("NewRendererFromFS: %v", err) } return r } func TestRendererLoads(t *testing.T) { r := newTestRenderer(t) purposes := r.Purposes() want := []string{"email_verify", "login_otp", "magic_link", "password_reset", "welcome"} if len(purposes) != len(want) { t.Fatalf("expected %d purposes, got %d: %v", len(want), len(purposes), purposes) } for i, p := range want { if purposes[i] != p { t.Errorf("purpose[%d]: want %q, got %q", i, p, purposes[i]) } } } func TestRenderLoginOTP(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("login_otp", emailpkg.EmailContext{ Code: "482916", ExpiresIn: 10, Purpose: "sign in", }) if err != nil { t.Fatalf("Render login_otp: %v", err) } if out.Subject == "" { t.Error("Subject is empty") } if !strings.Contains(out.Subject, "Test App") { t.Errorf("Subject %q does not contain app name", out.Subject) } if !strings.Contains(out.HTML, "482916") { t.Error("HTML does not contain OTP code") } if !strings.Contains(out.HTML, "code-box") { t.Error("HTML does not contain code-box element") } if out.PlainText == "" { t.Error("PlainText is empty") } if !strings.Contains(out.PlainText, "482916") { t.Error("PlainText does not contain OTP code") } if out.Preheader == "" { t.Error("Preheader is empty") } } func TestRenderMagicLink(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("magic_link", emailpkg.EmailContext{ ActionURL: "https://example.com/auth/verify?token=abc123", ButtonText: "Sign In \u2192", ExpiresIn: 15, }) if err != nil { t.Fatalf("Render magic_link: %v", err) } if !strings.Contains(out.HTML, "Sign In") { t.Error("HTML does not contain button text") } if !strings.Contains(out.HTML, "auth/verify") { t.Error("HTML does not contain action URL") } if out.PlainText == "" { t.Error("PlainText is empty") } } func TestRenderPasswordReset(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("password_reset", emailpkg.EmailContext{ ActionURL: "https://example.com/auth/reset?token=xyz789", ButtonText: "Reset Password \u2192", ExpiresIn: 60, }) if err != nil { t.Fatalf("Render password_reset: %v", err) } if !strings.Contains(out.HTML, "Reset Password") { t.Error("HTML does not contain button text") } if !strings.Contains(out.Subject, "Reset") { t.Errorf("Subject %q does not mention reset", out.Subject) } } func TestRenderVerifyEmail(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("email_verify", emailpkg.EmailContext{ Code: "738201", ExpiresIn: 30, Purpose: "verify your email", }) if err != nil { t.Fatalf("Render email_verify: %v", err) } if !strings.Contains(out.HTML, "738201") { t.Error("HTML does not contain verification code") } if !strings.Contains(out.HTML, "code-box") { t.Error("HTML does not contain code-box element") } } func TestRenderWelcome(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("welcome", emailpkg.EmailContext{ ActionURL: "https://example.com/dashboard", ButtonText: "Get Started \u2192", Name: "Jordan", }) if err != nil { t.Fatalf("Render welcome: %v", err) } if !strings.Contains(out.HTML, "Jordan") { t.Error("HTML does not contain user name") } if !strings.Contains(out.HTML, "Welcome") { t.Error("HTML does not contain welcome heading") } } func TestBrandColorInjection(t *testing.T) { r := newTestRenderer(t) out, err := r.Render("login_otp", emailpkg.EmailContext{ Code: "123456", ExpiresIn: 10, }) if err != nil { t.Fatalf("Render: %v", err) } // Brand primary color should appear in the inlined styles. if !strings.Contains(out.HTML, "#6366f1") { t.Error("HTML does not contain brand color #6366f1") } } func TestUnknownPurposeReturnsError(t *testing.T) { r := newTestRenderer(t) _, err := r.Render("nonexistent_type", emailpkg.EmailContext{}) if err == nil { t.Error("expected error for unknown purpose, got nil") } } func TestAllTemplatesHaveSubjectAndPreheader(t *testing.T) { r := newTestRenderer(t) contexts := map[string]emailpkg.EmailContext{ "login_otp": {Code: "111111", ExpiresIn: 10}, "magic_link": {ActionURL: "https://example.com/auth", ButtonText: "Sign In", ExpiresIn: 15}, "password_reset": {ActionURL: "https://example.com/reset", ButtonText: "Reset", ExpiresIn: 60}, "email_verify": {Code: "222222", ExpiresIn: 30}, "welcome": {ActionURL: "https://example.com", ButtonText: "Get Started", Name: "Alex"}, } for _, purpose := range r.Purposes() { ctx := contexts[purpose] out, err := r.Render(purpose, ctx) if err != nil { t.Errorf("%s: render error: %v", purpose, err) continue } if out.Subject == "" { t.Errorf("%s: Subject is empty", purpose) } if out.Preheader == "" { t.Errorf("%s: Preheader is empty", purpose) } if out.HTML == "" { t.Errorf("%s: HTML is empty", purpose) } if out.PlainText == "" { t.Errorf("%s: PlainText is empty", purpose) } } }