package user import ( "context" "net/url" "strings" "testing" "time" "git.golem15.com/golem15/sm-user-plugin/controllers" "git.golem15.com/golem15/sm-user-plugin/models" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/postcard" "gorm.io/gorm" ) type recordingMail struct { inner postcard.Mailer msgs []postcard.Message } func (r *recordingMail) Send(ctx context.Context, msg postcard.Message) error { r.msgs = append(r.msgs, msg) if r.inner == nil { return nil } return r.inner.Send(ctx, msg) } func TestMail(t *testing.T) { app, gdb := sessionApp(t, "0s") disableRegisterThrottle(t, app) if err := app.Config.Set("golem15.user.activation.activate_mode", "user"); err != nil { t.Fatal(err) } rec, mem := publishMemoryMail(t, app) postRegister(t, app, "198.51.100.10", `{"email":"mail-pl@example.test","password":"correct-horse","name":"Ada","preferred_locale":"pl"}`) postRegister(t, app, "198.51.100.11", `{"email":"mail-en@example.test","password":"correct-horse","name":"Grace","preferred_locale":"en"}`) assertTemplate(t, rec, "golem15.user::mail.activate", "mail-pl@example.test") assertTemplate(t, rec, "golem15.user::mail.activate-en", "mail-en@example.test") for _, msg := range rec.msgs { if !strings.HasPrefix(msg.Template, "golem15.user::mail.activate") { continue } link, _ := msg.Vars["link"].(string) code, _ := msg.Vars["code"].(string) decoded, err := url.QueryUnescape(link) if err != nil { t.Fatal(err) } if link == "" || code == "" || !strings.Contains(decoded, code) { t.Fatalf("activate vars template=%s link=%q code=%q", msg.Template, link, code) } } if len(mem.Messages()) < 2 { t.Fatalf("memory driver recorded %d messages", len(mem.Messages())) } before := len(rec.msgs) postJSON(t, controllers.ForgotPassword(app), "/_user/api/v1/forgot-password", `{"email":"mail-pl@example.test"}`) postJSON(t, controllers.ForgotPassword(app), "/_user/api/v1/forgot-password", `{"email":"mail-en@example.test"}`) assertTemplate(t, rec, "golem15.user::mail.restore", "mail-pl@example.test") assertTemplate(t, rec, "golem15.user::mail.restore-en", "mail-en@example.test") if len(mem.Messages()) < before+2 { t.Fatalf("memory driver recorded %d messages after restore", len(mem.Messages())) } softDelete(t, gdb, "react-pl@example.test", "pl") softDelete(t, gdb, "react-en@example.test", "en") postJSON(t, controllers.Login(app), "/_user/api/v1/login", `{"email":"react-pl@example.test","password":"correct-horse"}`) postJSON(t, controllers.Login(app), "/_user/api/v1/login", `{"email":"react-en@example.test","password":"correct-horse"}`) assertTemplate(t, rec, "golem15.user::mail.reactivate", "react-pl@example.test") assertTemplate(t, rec, "golem15.user::mail.reactivate-en", "react-en@example.test") } func publishMemoryMail(t *testing.T, app *backpack.App) (*recordingMail, *postcard.MemoryDriver) { t.Helper() cat := postcard.NewCatalog() p := &Plugin{} if err := cat.Register(p.ID(), p.MailTemplatesFS(), p.MailTemplates(), p.MailLayouts()); err != nil { t.Fatal(err) } mem := postcard.NewMemoryDriver() inner := postcard.NewMailer(cat, mem, postcard.Options{From: "noreply@example.test"}) rec := &recordingMail{inner: inner} if err := app.Publish[postcard.Mailer](rec); err != nil { t.Fatal(err) } return rec, mem } func assertTemplate(t *testing.T, rec *recordingMail, template, to string) { t.Helper() for _, msg := range rec.msgs { if msg.Template == template && len(msg.To) == 1 && msg.To[0] == to { return } } t.Fatalf("missing send %s to %s in %d messages", template, to, len(rec.msgs)) } func softDelete(t *testing.T, gdb *gorm.DB, email, locale string) { t.Helper() user := insertUser(t, gdb, email, "correct-horse") if err := gdb.Model(user).Update("preferred_locale", locale).Error; err != nil { t.Fatal(err) } if err := gdb.Model(&models.User{}).Where("id = ?", user.ID).Update("deleted_at", time.Now()).Error; err != nil { t.Fatal(err) } }