diff --git a/app/middlewares/setup.go b/app/middlewares/setup.go index b5cc7ff02..123d918fc 100644 --- a/app/middlewares/setup.go +++ b/app/middlewares/setup.go @@ -6,6 +6,7 @@ import ( "github.com/getfider/fider/app/pkg/email" "github.com/getfider/fider/app/pkg/email/mailgun" + "github.com/getfider/fider/app/pkg/email/noop" "github.com/getfider/fider/app/pkg/email/smtp" "github.com/getfider/fider/app/pkg/env" "github.com/getfider/fider/app/pkg/log" @@ -193,7 +194,7 @@ func WebSetup() web.MiddlewareFunc { func newEmailer(logger log.Logger) email.Sender { if env.IsTest() { - return email.NewNoopSender() + return noop.NewSender() } if env.IsDefined("EMAIL_MAILGUN_API") { return mailgun.NewSender(logger, web.NewHTTPClient(), env.MustGet("EMAIL_MAILGUN_DOMAIN"), env.MustGet("EMAIL_MAILGUN_API")) diff --git a/app/pkg/email/email.go b/app/pkg/email/email.go index 5cb8e13a2..6dd090311 100644 --- a/app/pkg/email/email.go +++ b/app/pkg/email/email.go @@ -95,23 +95,3 @@ type Sender interface { Send(tenant *models.Tenant, templateName string, params Params, from string, to Recipient) error BatchSend(tenant *models.Tenant, templateName string, params Params, from string, to []Recipient) error } - -// NoopSender does not send emails -type NoopSender struct { -} - -// NewNoopSender creates a new NoopSender -func NewNoopSender() *NoopSender { - return &NoopSender{} -} - -// Send an email -func (s *NoopSender) Send(tenant *models.Tenant, templateName string, params Params, from string, to Recipient) error { - return nil - -} - -// BatchSend an email to multiple recipients -func (s *NoopSender) BatchSend(tenant *models.Tenant, templateName string, params Params, from string, to []Recipient) error { - return nil -} diff --git a/app/pkg/email/noop/noop.go b/app/pkg/email/noop/noop.go new file mode 100644 index 000000000..11a406aba --- /dev/null +++ b/app/pkg/email/noop/noop.go @@ -0,0 +1,54 @@ +package noop + +import ( + "github.com/getfider/fider/app/models" + "github.com/getfider/fider/app/pkg/email" +) + +type request struct { + Tenant *models.Tenant + TemplateName string + Params email.Params + From string + To []email.Recipient +} + +// Sender does not send emails +type Sender struct { + Requests []*request +} + +// NewSender creates a new NoopSender +func NewSender() *Sender { + return &Sender{ + Requests: make([]*request, 0), + } +} + +// Send an email +func (s *Sender) Send(tenant *models.Tenant, templateName string, params email.Params, from string, to email.Recipient) error { + s.Requests = append(s.Requests, &request{ + Tenant: tenant, + TemplateName: templateName, + Params: params, + From: from, + To: []email.Recipient{to}, + }) + return nil + +} + +// BatchSend an email to multiple recipients +func (s *Sender) BatchSend(tenant *models.Tenant, templateName string, params email.Params, from string, to []email.Recipient) error { + if len(to) > 0 { + s.Requests = append(s.Requests, &request{ + Tenant: tenant, + TemplateName: templateName, + Params: params, + From: from, + To: to, + }) + } + + return nil +} diff --git a/app/pkg/markdown/markdown.go b/app/pkg/markdown/markdown.go index fa1e52909..6e1f6f3ed 100644 --- a/app/pkg/markdown/markdown.go +++ b/app/pkg/markdown/markdown.go @@ -28,7 +28,8 @@ var htmlExtns = 0 | func Parse(input string) template.HTML { renderer := blackfriday.HtmlRenderer(htmlExtns, "", "") output := blackfriday.Markdown([]byte(input), renderer, mdExtns) - return template.HTML(output) + + return template.HTML(strings.TrimSpace(string(output))) } // PlainText parses given markdown input and return only the text diff --git a/app/pkg/markdown/markdown_test.go b/app/pkg/markdown/markdown_test.go index 2fb3a32bc..11260c36c 100644 --- a/app/pkg/markdown/markdown_test.go +++ b/app/pkg/markdown/markdown_test.go @@ -12,12 +12,9 @@ func TestParseMarkdown(t *testing.T) { RegisterT(t) for input, expected := range map[string]string{ - "# Hello World": `

Hello World

-`, - "![](http://example.com/hello.jpg)": `

-`, - "Go to http://example.com/hello.jpg": `

Go to http://example.com/hello.jpg

-`, + "# Hello World": `

Hello World

`, + "![](http://example.com/hello.jpg)": `

`, + "Go to http://example.com/hello.jpg": `

Go to http://example.com/hello.jpg

`, ` - **Option 1** - *Option 2* @@ -25,8 +22,7 @@ func TestParseMarkdown(t *testing.T) {
  • Option 1
  • Option 2
  • Option 3
  • - -`, +`, `Please add: – SEND_SMS – RECEIVE_SMS @@ -39,8 +35,7 @@ Thanks!`: `

    Please add:
    – READ_PHONE_STATE
    This will allow to send and receive SMS and get the IMEI No. in our app.

    -

    Thanks!

    -`, +

    Thanks!

    `, } { output := markdown.Parse(input) Expect(output).Equals(template.HTML(expected)) diff --git a/app/pkg/mock/setup.go b/app/pkg/mock/setup.go index d965a136b..a6ee0ef1f 100644 --- a/app/pkg/mock/setup.go +++ b/app/pkg/mock/setup.go @@ -5,7 +5,7 @@ import ( "github.com/getfider/fider/app" "github.com/getfider/fider/app/models" - "github.com/getfider/fider/app/pkg/email" + "github.com/getfider/fider/app/pkg/email/noop" "github.com/getfider/fider/app/pkg/oauth" "github.com/getfider/fider/app/storage/inmemory" ) @@ -53,7 +53,7 @@ func createServices(seed bool) *app.Services { Notifications: inmemory.NewNotificationStorage(), Ideas: inmemory.NewIdeaStorage(), OAuth: &OAuthService{}, - Emailer: email.NewNoopSender(), + Emailer: noop.NewSender(), } if seed { diff --git a/app/pkg/mock/worker.go b/app/pkg/mock/worker.go index 3edb602ff..273a4b3e7 100644 --- a/app/pkg/mock/worker.go +++ b/app/pkg/mock/worker.go @@ -12,6 +12,7 @@ type Worker struct { tenant *models.Tenant user *models.User services *app.Services + baseURL string } func createWorker(services *app.Services) *Worker { @@ -32,12 +33,19 @@ func (w *Worker) AsUser(user *models.User) *Worker { return w } +// WithBaseURL set current context baseURL +func (w *Worker) WithBaseURL(baseURL string) *Worker { + w.baseURL = baseURL + return w +} + // Execute given task with current context func (w *Worker) Execute(task worker.Task) error { context := worker.NewContext("0", task.Name, nil, noop.NewLogger()) context.SetServices(w.services) context.SetUser(w.user) context.SetTenant(w.tenant) + context.SetBaseURL(w.baseURL) return task.Job(context) } diff --git a/app/storage/inmemory/idea.go b/app/storage/inmemory/idea.go index 17ed43242..0d4a6ee3c 100644 --- a/app/storage/inmemory/idea.go +++ b/app/storage/inmemory/idea.go @@ -1,7 +1,6 @@ package inmemory import ( - "fmt" "time" "github.com/gosimple/slug" @@ -16,7 +15,7 @@ type IdeaStorage struct { lastCommentID int ideas []*models.Idea ideasSupportedBy map[int][]int - ideaSubscribers map[int][]int + ideaSubscribers map[int][]*models.User ideaComments map[int][]*models.Comment tenant *models.Tenant user *models.User @@ -27,7 +26,7 @@ func NewIdeaStorage() *IdeaStorage { return &IdeaStorage{ ideas: make([]*models.Idea, 0), ideasSupportedBy: make(map[int][]int, 0), - ideaSubscribers: make(map[int][]int, 0), + ideaSubscribers: make(map[int][]*models.User, 0), ideaComments: make(map[int][]*models.Comment, 0), } } @@ -112,6 +111,7 @@ func (s *IdeaStorage) Add(title, description string) (*models.Idea, error) { } s.ideas = append(s.ideas, idea) s.ideasSupportedBy[s.user.ID] = append(s.ideasSupportedBy[s.user.ID], idea.ID) + s.AddSubscriber(idea, s.user) return idea, nil } @@ -225,15 +225,15 @@ func (s *IdeaStorage) SupportedBy() ([]int, error) { // AddSubscriber adds user to the idea list of subscribers func (s *IdeaStorage) AddSubscriber(idea *models.Idea, user *models.User) error { - s.ideaSubscribers[idea.ID] = append(s.ideaSubscribers[idea.ID], user.ID) + s.ideaSubscribers[idea.ID] = append(s.ideaSubscribers[idea.ID], user) return nil } // RemoveSubscriber removes user from idea list of subscribers func (s *IdeaStorage) RemoveSubscriber(idea *models.Idea, user *models.User) error { - for i, id := range s.ideaSubscribers[idea.ID] { - if id == user.ID { - s.ideaSubscribers[idea.ID] = append(s.ideasSupportedBy[idea.ID][:i], s.ideasSupportedBy[idea.ID][i+1:]...) + for i, u := range s.ideaSubscribers[idea.ID] { + if u.ID == user.ID { + s.ideaSubscribers[idea.ID] = append(s.ideaSubscribers[idea.ID][:i], s.ideaSubscribers[idea.ID][i+1:]...) break } } @@ -249,13 +249,10 @@ func (s *IdeaStorage) GetActiveSubscribers(number int, channel models.Notificati subscribers, ok := s.ideaSubscribers[idea.ID] if ok { users := make([]*models.User, len(subscribers)) - for i, id := range subscribers { - users[i] = &models.User{ - ID: id, - Name: fmt.Sprintf("User %d", id), - Email: fmt.Sprintf("user%d@test.com", id), - } + for i, user := range subscribers { + users[i] = user } + return users, nil } return make([]*models.User, 0), nil } diff --git a/app/tasks/tasks.go b/app/tasks/tasks.go index 864f65a7b..7da093736 100644 --- a/app/tasks/tasks.go +++ b/app/tasks/tasks.go @@ -76,8 +76,10 @@ func NotifyAboutNewIdea(idea *models.Idea) worker.Task { title := fmt.Sprintf("New idea: **%s**", idea.Title) link := fmt.Sprintf("/ideas/%d/%s", idea.Number, idea.Slug) for _, user := range users { - if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { - return c.Failure(err) + if user.ID != c.User().ID { + if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { + return c.Failure(err) + } } } @@ -117,8 +119,10 @@ func NotifyAboutNewComment(idea *models.Idea, comment *models.NewComment) worker title := fmt.Sprintf("**%s** left a comment on **%s**", c.User().Name, idea.Title) link := fmt.Sprintf("/ideas/%d/%s", idea.Number, idea.Slug) for _, user := range users { - if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { - return c.Failure(err) + if user.ID != c.User().ID { + if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { + return c.Failure(err) + } } } @@ -164,8 +168,10 @@ func NotifyAboutStatusChange(idea *models.Idea, prevStatus int) worker.Task { title := fmt.Sprintf("**%s** changed status of **%s** to **%s**", c.User().Name, idea.Title, models.GetIdeaStatusName(idea.Status)) link := fmt.Sprintf("/ideas/%d/%s", idea.Number, idea.Slug) for _, user := range users { - if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { - return c.Failure(err) + if user.ID != c.User().ID { + if _, err = c.Services().Notifications.Insert(user, title, link, idea.ID); err != nil { + return c.Failure(err) + } } } @@ -215,7 +221,7 @@ func SendInvites(subject, message string, invitations []*models.UserInvitation) return c.Failure(err) } - url := link(c.BaseURL(), "/invite/verify?k=%s", invite.VerificationKey) + url := fmt.Sprintf("%s/invite/verify?k=%s", c.BaseURL(), invite.VerificationKey) toMessage := strings.Replace(message, app.InvitePlaceholder, string(url), -1) to[i] = email.NewRecipient("", invite.Email, email.Params{ "message": markdown.Parse(toMessage), diff --git a/app/tasks/tasks_test.go b/app/tasks/tasks_test.go index 692bf8a7e..4defc05ca 100644 --- a/app/tasks/tasks_test.go +++ b/app/tasks/tasks_test.go @@ -1,8 +1,13 @@ package tasks_test import ( + "html/template" "testing" + "time" + "github.com/getfider/fider/app/pkg/email" + + "github.com/getfider/fider/app/pkg/email/noop" "github.com/getfider/fider/app/pkg/mock" "github.com/getfider/fider/app/models" @@ -13,39 +18,326 @@ import ( func TestSendSignUpEmailTask(t *testing.T) { RegisterT(t) - worker, _ := mock.NewWorker() - task := tasks.SendSignUpEmail(&models.CreateTenant{}, "http://anywhere.com") + worker, services := mock.NewWorker() + emailer := services.Emailer.(*noop.Sender) + task := tasks.SendSignUpEmail(&models.CreateTenant{ + VerificationKey: "1234", + }, "http://domain.com") + err := worker. AsUser(mock.JonSnow). Execute(task) + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("signup_email") + Expect(emailer.Requests[0].Tenant).IsNil() + Expect(emailer.Requests[0].Params).Equals(email.Params{}) + Expect(emailer.Requests[0].From).Equals("Fider") + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Params: email.Params{ + "link": template.HTML("http://domain.com/signup/verify?k=1234"), + }, + }) } func TestSendSignInEmailTask(t *testing.T) { RegisterT(t) - worker, _ := mock.NewWorker() - task := tasks.SendSignInEmail(&models.SignInByEmail{}) + worker, services := mock.NewWorker() + emailer := services.Emailer.(*noop.Sender) + task := tasks.SendSignInEmail(&models.SignInByEmail{ + VerificationKey: "9876", + }) + err := worker. OnTenant(mock.DemoTenant). AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). Execute(task) + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("signin_email") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{}) + Expect(emailer.Requests[0].From).Equals(mock.DemoTenant.Name) + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Params: email.Params{ + "tenantName": mock.DemoTenant.Name, + "link": template.HTML("http://domain.com/signin/verify?k=9876"), + }, + }) } func TestSendChangeEmailConfirmationTask(t *testing.T) { RegisterT(t) - worker, _ := mock.NewWorker() + worker, services := mock.NewWorker() + emailer := services.Emailer.(*noop.Sender) task := tasks.SendChangeEmailConfirmation(&models.ChangeUserEmail{ - Requestor: &models.User{ - Name: "Some User", - Email: "some@domain.com", + Email: "newemail@domain.com", + VerificationKey: "13579", + Requestor: mock.JonSnow, + }) + + err := worker. + OnTenant(mock.DemoTenant). + AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). + Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("change_emailaddress_email") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{}) + Expect(emailer.Requests[0].From).Equals(mock.DemoTenant.Name) + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Name: "Jon Snow", + Address: "newemail@domain.com", + Params: email.Params{ + "name": "Jon Snow", + "oldEmail": "jon.snow@got.com", + "newEmail": "newemail@domain.com", + "link": template.HTML("http://domain.com/change-email/verify?k=13579"), }, }) +} + +func TestNotifyAboutNewIdeaTask(t *testing.T) { + RegisterT(t) + + worker, services := mock.NewWorker() + services.SetCurrentUser(mock.JonSnow) + idea, _ := services.Ideas.Add("Add support for TypeScript", "TypeScript is great, please add support for it") + + services.Ideas.AddSubscriber(idea, mock.AryaStark) + emailer := services.Emailer.(*noop.Sender) + task := tasks.NotifyAboutNewIdea(idea) + err := worker. OnTenant(mock.DemoTenant). AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("new_idea") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{ + "title": "[Demonstration] Add support for TypeScript", + "content": template.HTML("

    TypeScript is great, please add support for it

    "), + "view": template.HTML("View it on your browser"), + "change": template.HTML("change your notification settings"), + }) + Expect(emailer.Requests[0].From).Equals("Jon Snow") + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Name: "Arya Stark", + Address: "arya.stark@got.com", + Params: email.Params{}, + }) + + services.SetCurrentUser(mock.AryaStark) + notifications, err := services.Notifications.GetActiveNotifications() Expect(err).IsNil() + Expect(notifications).HasLen(1) + Expect(notifications[0].ID).Equals(1) + Expect(notifications[0].CreatedOn).TemporarilySimilar(time.Now(), 5*time.Second) + Expect(notifications[0].Link).Equals("/ideas/1/add-support-for-typescript") + Expect(notifications[0].Read).IsFalse() + Expect(notifications[0].Title).Equals("New idea: **Add support for TypeScript**") +} + +func TestNotifyAboutNewCommentTask(t *testing.T) { + RegisterT(t) + + worker, services := mock.NewWorker() + services.SetCurrentUser(mock.JonSnow) + idea, _ := services.Ideas.Add("Add support for TypeScript", "TypeScript is great, please add support for it") + comment := &models.NewComment{ + Number: idea.Number, + Content: "I agree", + } + + emailer := services.Emailer.(*noop.Sender) + task := tasks.NotifyAboutNewComment(idea, comment) + + err := worker. + OnTenant(mock.DemoTenant). + AsUser(mock.AryaStark). + WithBaseURL("http://domain.com"). + Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("new_comment") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{ + "title": "[Demonstration] Add support for TypeScript", + "content": template.HTML("

    I agree

    "), + "view": template.HTML("View it on your browser"), + "change": template.HTML("change your notification settings"), + "unsubscribe": template.HTML("unsubscribe from it"), + }) + Expect(emailer.Requests[0].From).Equals("Arya Stark") + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Name: "Jon Snow", + Address: "jon.snow@got.com", + Params: email.Params{}, + }) + + services.SetCurrentUser(mock.JonSnow) + notifications, err := services.Notifications.GetActiveNotifications() + Expect(err).IsNil() + Expect(notifications).HasLen(1) + Expect(notifications[0].ID).Equals(1) + Expect(notifications[0].CreatedOn).TemporarilySimilar(time.Now(), 5*time.Second) + Expect(notifications[0].Link).Equals("/ideas/1/add-support-for-typescript") + Expect(notifications[0].Read).IsFalse() + Expect(notifications[0].Title).Equals("**Arya Stark** left a comment on **Add support for TypeScript**") +} + +func TestNotifyAboutStatusChangeTask(t *testing.T) { + RegisterT(t) + + worker, services := mock.NewWorker() + services.SetCurrentUser(mock.AryaStark) + idea, _ := services.Ideas.Add("Add support for TypeScript", "TypeScript is great, please add support for it") + services.Ideas.SetResponse(idea, "Planned for next release.", models.IdeaPlanned) + + emailer := services.Emailer.(*noop.Sender) + task := tasks.NotifyAboutStatusChange(idea, models.IdeaOpen) + + err := worker. + OnTenant(mock.DemoTenant). + AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). + Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("change_status") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{ + "title": "[Demonstration] Add support for TypeScript", + "content": template.HTML("

    Planned for next release.

    "), + "duplicate": template.HTML(""), + "status": "Planned", + "view": template.HTML("View it on your browser"), + "change": template.HTML("change your notification settings"), + "unsubscribe": template.HTML("unsubscribe from it"), + }) + Expect(emailer.Requests[0].From).Equals("Jon Snow") + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Name: "Arya Stark", + Address: "arya.stark@got.com", + Params: email.Params{}, + }) + + services.SetCurrentUser(mock.AryaStark) + notifications, err := services.Notifications.GetActiveNotifications() + Expect(err).IsNil() + Expect(notifications).HasLen(1) + Expect(notifications[0].ID).Equals(1) + Expect(notifications[0].CreatedOn).TemporarilySimilar(time.Now(), 5*time.Second) + Expect(notifications[0].Link).Equals("/ideas/1/add-support-for-typescript") + Expect(notifications[0].Read).IsFalse() + Expect(notifications[0].Title).Equals("**Jon Snow** changed status of **Add support for TypeScript** to **Planned**") +} + +func TestNotifyAboutStatusChangeTask_Duplicate(t *testing.T) { + RegisterT(t) + + worker, services := mock.NewWorker() + services.SetCurrentUser(mock.AryaStark) + idea1, _ := services.Ideas.Add("Add support for TypeScript", "TypeScript is great, please add support for it") + idea2, _ := services.Ideas.Add("I need TypeScript", "") + services.Ideas.MarkAsDuplicate(idea2, idea1) + + emailer := services.Emailer.(*noop.Sender) + task := tasks.NotifyAboutStatusChange(idea2, models.IdeaOpen) + + err := worker. + OnTenant(mock.DemoTenant). + AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). + Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("change_status") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{ + "title": "[Demonstration] I need TypeScript", + "content": template.HTML(""), + "duplicate": template.HTML("Add support for TypeScript"), + "status": "Duplicate", + "view": template.HTML("View it on your browser"), + "change": template.HTML("change your notification settings"), + "unsubscribe": template.HTML("unsubscribe from it"), + }) + Expect(emailer.Requests[0].From).Equals("Jon Snow") + Expect(emailer.Requests[0].To).HasLen(1) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Name: "Arya Stark", + Address: "arya.stark@got.com", + Params: email.Params{}, + }) + + services.SetCurrentUser(mock.AryaStark) + notifications, err := services.Notifications.GetActiveNotifications() + Expect(err).IsNil() + Expect(notifications).HasLen(1) + Expect(notifications[0].ID).Equals(1) + Expect(notifications[0].CreatedOn).TemporarilySimilar(time.Now(), 5*time.Second) + Expect(notifications[0].Link).Equals("/ideas/2/i-need-typescript") + Expect(notifications[0].Read).IsFalse() + Expect(notifications[0].Title).Equals("**Jon Snow** changed status of **I need TypeScript** to **Duplicate**") +} + +func TestSendInvites(t *testing.T) { + RegisterT(t) + + worker, services := mock.NewWorker() + emailer := services.Emailer.(*noop.Sender) + task := tasks.SendInvites("My Subject", "Click here: %invite%", []*models.UserInvitation{ + &models.UserInvitation{Email: "user1@domain.com", VerificationKey: "1234"}, + &models.UserInvitation{Email: "user2@domain.com", VerificationKey: "5678"}, + }) + + err := worker. + OnTenant(mock.DemoTenant). + AsUser(mock.JonSnow). + WithBaseURL("http://domain.com"). + Execute(task) + + Expect(err).IsNil() + Expect(emailer.Requests).HasLen(1) + Expect(emailer.Requests[0].TemplateName).Equals("invite_email") + Expect(emailer.Requests[0].Tenant).Equals(mock.DemoTenant) + Expect(emailer.Requests[0].Params).Equals(email.Params{ + "subject": "My Subject", + }) + Expect(emailer.Requests[0].From).Equals("Jon Snow") + Expect(emailer.Requests[0].To).HasLen(2) + Expect(emailer.Requests[0].To[0]).Equals(email.Recipient{ + Address: "user1@domain.com", + Params: email.Params{ + "message": template.HTML(`

    Click here: http://domain.com/invite/verify?k=1234

    `), + }, + }) + Expect(emailer.Requests[0].To[1]).Equals(email.Recipient{ + Address: "user2@domain.com", + Params: email.Params{ + "message": template.HTML(`

    Click here: http://domain.com/invite/verify?k=5678

    `), + }, + }) }