Files
Stephan D d367dddbbd
Some checks failed
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx/1 Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/fx/2 Pipeline failed
fx build fix
2025-11-08 00:40:01 +01:00

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