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") } }