542 lines
13 KiB
Go
542 lines
13 KiB
Go
package notificationimp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/tech/sendico/notification/interface/api/localizer"
|
|
mmail "github.com/tech/sendico/notification/internal/server/notificationimp/mail/messagebuilder"
|
|
"github.com/tech/sendico/pkg/db/storable"
|
|
"github.com/tech/sendico/pkg/domainprovider"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mlogger/factory"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
// Mock implementations
|
|
|
|
type mockMailClient struct {
|
|
sendFunc func(r mmail.MailBuilder) error
|
|
mailBuilderFunc func() mmail.MailBuilder
|
|
sentMessages []mockSentMessage
|
|
}
|
|
|
|
type mockSentMessage struct {
|
|
accountID string
|
|
templateID string
|
|
locale string
|
|
recipients []string
|
|
data map[string]string
|
|
buttonLink string
|
|
}
|
|
|
|
func (m *mockMailClient) Send(r mmail.MailBuilder) error {
|
|
if m.sendFunc != nil {
|
|
return m.sendFunc(r)
|
|
}
|
|
// Record the message for verification
|
|
msg, _ := r.Build()
|
|
if msg != nil {
|
|
sent := mockSentMessage{
|
|
accountID: msg.AccountID(),
|
|
templateID: msg.TemplateID(),
|
|
locale: msg.Locale(),
|
|
recipients: msg.Recipients(),
|
|
data: make(map[string]string),
|
|
}
|
|
// Extract string parameters
|
|
for k, v := range msg.Parameters() {
|
|
if str, ok := v.(string); ok {
|
|
sent.data[k] = str
|
|
}
|
|
}
|
|
m.sentMessages = append(m.sentMessages, sent)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *mockMailClient) MailBuilder() mmail.MailBuilder {
|
|
if m.mailBuilderFunc != nil {
|
|
return m.mailBuilderFunc()
|
|
}
|
|
return &mockMailBuilder{
|
|
accountID: "",
|
|
templateID: "",
|
|
locale: "",
|
|
recipients: []string{},
|
|
data: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
type mockMailBuilder struct {
|
|
accountID string
|
|
templateID string
|
|
locale string
|
|
recipients []string
|
|
buttonLink string
|
|
data map[string]string
|
|
}
|
|
|
|
func (m *mockMailBuilder) SetAccountID(accountID string) mmail.MailBuilder {
|
|
m.accountID = accountID
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) SetTemplateID(templateID string) mmail.MailBuilder {
|
|
m.templateID = templateID
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) SetLocale(locale string) mmail.MailBuilder {
|
|
m.locale = locale
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) AddRecipient(recipientName, recipient string) mmail.MailBuilder {
|
|
m.recipients = append(m.recipients, recipient)
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) AddButton(link string) mmail.MailBuilder {
|
|
m.buttonLink = link
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) AddData(key, value string) mmail.MailBuilder {
|
|
m.data[key] = value
|
|
return m
|
|
}
|
|
|
|
func (m *mockMailBuilder) Build() (mmail.Message, error) {
|
|
if len(m.recipients) == 0 {
|
|
return nil, errors.New("recipient not set")
|
|
}
|
|
return &mockMessage{
|
|
accountID: m.accountID,
|
|
templateID: m.templateID,
|
|
locale: m.locale,
|
|
recipients: m.recipients,
|
|
parameters: convertToAnyMap(m.data),
|
|
}, nil
|
|
}
|
|
|
|
type mockMessage struct {
|
|
accountID string
|
|
templateID string
|
|
locale string
|
|
recipients []string
|
|
parameters map[string]any
|
|
}
|
|
|
|
func (m *mockMessage) AccountID() string { return m.accountID }
|
|
func (m *mockMessage) TemplateID() string { return m.templateID }
|
|
func (m *mockMessage) Locale() string { return m.locale }
|
|
func (m *mockMessage) Recipients() []string { return m.recipients }
|
|
func (m *mockMessage) Parameters() map[string]any { return m.parameters }
|
|
func (m *mockMessage) Body(l localizer.Localizer, dp domainprovider.DomainProvider) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func convertToAnyMap(m map[string]string) map[string]any {
|
|
result := make(map[string]any)
|
|
for k, v := range m {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
type mockDomainProvider struct {
|
|
getFullLinkFunc func(linkElem ...string) (string, error)
|
|
}
|
|
|
|
func (m *mockDomainProvider) GetFullLink(linkElem ...string) (string, error) {
|
|
if m.getFullLinkFunc != nil {
|
|
return m.getFullLinkFunc(linkElem...)
|
|
}
|
|
return "https://example.com/link", nil
|
|
}
|
|
|
|
func (m *mockDomainProvider) GetAPILink(linkElem ...string) (string, error) {
|
|
return "https://api.example.com/link", nil
|
|
}
|
|
|
|
// Tests for onAccount handler
|
|
|
|
func TestOnAccount_ValidAccount_SendsWelcomeEmail(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
VerifyToken: "test-verify-token",
|
|
}
|
|
|
|
err := api.onAccount(context.Background(), account)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 1 {
|
|
t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
|
|
}
|
|
|
|
sent := mockClient.sentMessages[0]
|
|
if sent.templateID != "welcome" {
|
|
t.Errorf("Expected template 'welcome', got '%s'", sent.templateID)
|
|
}
|
|
if sent.locale != "en-US" {
|
|
t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
|
|
}
|
|
if len(sent.recipients) != 1 || sent.recipients[0] != "user@example.com" {
|
|
t.Errorf("Expected recipient 'user@example.com', got %v", sent.recipients)
|
|
}
|
|
}
|
|
|
|
func TestOnAccount_LinkGenerationFails_ReturnsError(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{
|
|
getFullLinkFunc: func(linkElem ...string) (string, error) {
|
|
return "", errors.New("link generation failed")
|
|
},
|
|
}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
VerifyToken: "test-verify-token",
|
|
}
|
|
|
|
err := api.onAccount(context.Background(), account)
|
|
|
|
if err == nil {
|
|
t.Fatal("Expected error from link generation failure")
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 0 {
|
|
t.Error("No message should be sent when link generation fails")
|
|
}
|
|
}
|
|
|
|
func TestOnAccount_SendFails_ReturnsError(t *testing.T) {
|
|
mockClient := &mockMailClient{
|
|
sendFunc: func(r mmail.MailBuilder) error {
|
|
return errors.New("send failed")
|
|
},
|
|
}
|
|
mockDP := &mockDomainProvider{}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
VerifyToken: "test-verify-token",
|
|
}
|
|
|
|
err := api.onAccount(context.Background(), account)
|
|
|
|
if err == nil {
|
|
t.Fatal("Expected error from send failure")
|
|
}
|
|
}
|
|
|
|
// Tests for onInvitation handler
|
|
|
|
func TestOnInvitation_ValidInvitation_SendsInvitationEmail(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Inviter User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
}
|
|
|
|
invitationID := primitive.NewObjectID()
|
|
invitation := &model.Invitation{}
|
|
invitation.ID = invitationID
|
|
invitation.Content.Email = "invitee@example.com"
|
|
invitation.Content.Name = "Invitee Name"
|
|
|
|
err := api.onInvitation(context.Background(), account, invitation)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 1 {
|
|
t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
|
|
}
|
|
|
|
sent := mockClient.sentMessages[0]
|
|
if sent.templateID != "invitation" {
|
|
t.Errorf("Expected template 'invitation', got '%s'", sent.templateID)
|
|
}
|
|
if sent.locale != "en-US" {
|
|
t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
|
|
}
|
|
if len(sent.recipients) != 1 || sent.recipients[0] != "invitee@example.com" {
|
|
t.Errorf("Expected recipient 'invitee@example.com', got %v", sent.recipients)
|
|
}
|
|
if sent.data["InviterName"] != "Inviter User" {
|
|
t.Errorf("Expected InviterName 'Inviter User', got '%s'", sent.data["InviterName"])
|
|
}
|
|
if sent.data["Name"] != "Invitee Name" {
|
|
t.Errorf("Expected Name 'Invitee Name', got '%s'", sent.data["Name"])
|
|
}
|
|
}
|
|
|
|
func TestOnInvitation_LinkGenerationFails_ReturnsError(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{
|
|
getFullLinkFunc: func(linkElem ...string) (string, error) {
|
|
return "", errors.New("link generation failed")
|
|
},
|
|
}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Inviter User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
}
|
|
|
|
invitationID := primitive.NewObjectID()
|
|
invitation := &model.Invitation{}
|
|
invitation.ID = invitationID
|
|
invitation.Content.Email = "invitee@example.com"
|
|
invitation.Content.Name = "Invitee Name"
|
|
|
|
err := api.onInvitation(context.Background(), account, invitation)
|
|
|
|
if err == nil {
|
|
t.Fatal("Expected error from link generation failure")
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 0 {
|
|
t.Error("No message should be sent when link generation fails")
|
|
}
|
|
}
|
|
|
|
// Tests for onPasswordReset handler
|
|
|
|
func TestOnPasswordReset_ValidReset_SendsResetEmail(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
}
|
|
|
|
resetToken := "reset-token-123"
|
|
|
|
err := api.onPasswordReset(context.Background(), account, resetToken)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 1 {
|
|
t.Fatalf("Expected 1 message sent, got %d", len(mockClient.sentMessages))
|
|
}
|
|
|
|
sent := mockClient.sentMessages[0]
|
|
if sent.templateID != "reset-password" {
|
|
t.Errorf("Expected template 'reset-password', got '%s'", sent.templateID)
|
|
}
|
|
if sent.locale != "en-US" {
|
|
t.Errorf("Expected locale 'en-US', got '%s'", sent.locale)
|
|
}
|
|
if len(sent.recipients) != 1 || sent.recipients[0] != "user@example.com" {
|
|
t.Errorf("Expected recipient 'user@example.com', got %v", sent.recipients)
|
|
}
|
|
if sent.data["URL"] == "" {
|
|
t.Error("Expected URL parameter to be set")
|
|
}
|
|
}
|
|
|
|
func TestOnPasswordReset_LinkGenerationFails_ReturnsError(t *testing.T) {
|
|
mockClient := &mockMailClient{}
|
|
mockDP := &mockDomainProvider{
|
|
getFullLinkFunc: func(linkElem ...string) (string, error) {
|
|
return "", errors.New("link generation failed")
|
|
},
|
|
}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
}
|
|
|
|
err := api.onPasswordReset(context.Background(), account, "reset-token")
|
|
|
|
if err == nil {
|
|
t.Fatal("Expected error from link generation failure")
|
|
}
|
|
|
|
if len(mockClient.sentMessages) != 0 {
|
|
t.Error("No message should be sent when link generation fails")
|
|
}
|
|
}
|
|
|
|
func TestOnPasswordReset_SendFails_ReturnsError(t *testing.T) {
|
|
mockClient := &mockMailClient{
|
|
sendFunc: func(r mmail.MailBuilder) error {
|
|
return errors.New("send failed")
|
|
},
|
|
}
|
|
mockDP := &mockDomainProvider{}
|
|
|
|
api := &NotificationAPI{
|
|
logger: mlogger.NewLogger(true),
|
|
client: mockClient,
|
|
dp: mockDP,
|
|
}
|
|
|
|
account := &model.Account{
|
|
AccountPublic: model.AccountPublic{
|
|
AccountBase: model.AccountBase{
|
|
Base: storable.Base{
|
|
ID: primitive.NewObjectID(),
|
|
},
|
|
Describable: model.Describable{
|
|
Name: "Test User",
|
|
},
|
|
},
|
|
UserDataBase: model.UserDataBase{
|
|
Login: "user@example.com",
|
|
Locale: "en-US",
|
|
},
|
|
},
|
|
}
|
|
|
|
err := api.onPasswordReset(context.Background(), account, "reset-token")
|
|
|
|
if err == nil {
|
|
t.Fatal("Expected error from send failure")
|
|
}
|
|
}
|