diff --git a/src/Core/API/API.Specs/Mocks/MockEmailProvider.cs b/src/Core/API/API.Specs/Mocks/MockEmailProvider.cs
new file mode 100644
index 0000000..28ed309
--- /dev/null
+++ b/src/Core/API/API.Specs/Mocks/MockEmailProvider.cs
@@ -0,0 +1,54 @@
+using Infrastructure.Email;
+
+namespace API.Specs.Mocks;
+
+///
+/// Mock email provider for testing that doesn't actually send emails.
+/// Tracks sent emails for verification in tests if needed.
+///
+public class MockEmailProvider : IEmailProvider
+{
+ public List SentEmails { get; } = new();
+
+ public Task SendAsync(string to, string subject, string body, bool isHtml = true)
+ {
+ SentEmails.Add(new SentEmail
+ {
+ To = [to],
+ Subject = subject,
+ Body = body,
+ IsHtml = isHtml,
+ SentAt = DateTime.UtcNow
+ });
+
+ return Task.CompletedTask;
+ }
+
+ public Task SendAsync(IEnumerable to, string subject, string body, bool isHtml = true)
+ {
+ SentEmails.Add(new SentEmail
+ {
+ To = to.ToList(),
+ Subject = subject,
+ Body = body,
+ IsHtml = isHtml,
+ SentAt = DateTime.UtcNow
+ });
+
+ return Task.CompletedTask;
+ }
+
+ public void Clear()
+ {
+ SentEmails.Clear();
+ }
+
+ public class SentEmail
+ {
+ public List To { get; init; } = new();
+ public string Subject { get; init; } = string.Empty;
+ public string Body { get; init; } = string.Empty;
+ public bool IsHtml { get; init; }
+ public DateTime SentAt { get; init; }
+ }
+}
diff --git a/src/Core/API/API.Specs/Mocks/MockEmailService.cs b/src/Core/API/API.Specs/Mocks/MockEmailService.cs
index e864a0a..b320288 100644
--- a/src/Core/API/API.Specs/Mocks/MockEmailService.cs
+++ b/src/Core/API/API.Specs/Mocks/MockEmailService.cs
@@ -1,37 +1,18 @@
-using Infrastructure.Email;
+using Domain.Entities;
+using Service.Emails;
namespace API.Specs.Mocks;
-///
-/// Mock email service for testing that doesn't actually send emails.
-/// Tracks sent emails for verification in tests if needed.
-///
-public class MockEmailProvider : IEmailProvider
+public class MockEmailService : IEmailService
{
- public List SentEmails { get; } = new();
+ public List SentRegistrationEmails { get; } = new();
- public Task SendAsync(string to, string subject, string body, bool isHtml = true)
+ public Task SendRegistrationEmailAsync(UserAccount createdUser, string confirmationToken)
{
- SentEmails.Add(new SentEmail
+ SentRegistrationEmails.Add(new RegistrationEmail
{
- To = [to],
- Subject = subject,
- Body = body,
- IsHtml = isHtml,
- SentAt = DateTime.UtcNow
- });
-
- return Task.CompletedTask;
- }
-
- public Task SendAsync(IEnumerable to, string subject, string body, bool isHtml = true)
- {
- SentEmails.Add(new SentEmail
- {
- To = to.ToList(),
- Subject = subject,
- Body = body,
- IsHtml = isHtml,
+ UserAccount = createdUser,
+ ConfirmationToken = confirmationToken,
SentAt = DateTime.UtcNow
});
@@ -40,15 +21,13 @@ public class MockEmailProvider : IEmailProvider
public void Clear()
{
- SentEmails.Clear();
+ SentRegistrationEmails.Clear();
}
- public class SentEmail
+ public class RegistrationEmail
{
- public List To { get; init; } = new();
- public string Subject { get; init; } = string.Empty;
- public string Body { get; init; } = string.Empty;
- public bool IsHtml { get; init; }
+ public UserAccount UserAccount { get; init; } = null!;
+ public string ConfirmationToken { get; init; } = string.Empty;
public DateTime SentAt { get; init; }
}
}
diff --git a/src/Core/API/API.Specs/TestApiFactory.cs b/src/Core/API/API.Specs/TestApiFactory.cs
index ad63065..fef08d0 100644
--- a/src/Core/API/API.Specs/TestApiFactory.cs
+++ b/src/Core/API/API.Specs/TestApiFactory.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Service.Emails;
namespace API.Specs
{
@@ -16,16 +17,27 @@ namespace API.Specs
builder.ConfigureServices(services =>
{
- // Replace the real email service with mock for testing
- var descriptor = services.SingleOrDefault(
+ // Replace the real email provider with mock for testing
+ var emailProviderDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(IEmailProvider));
- if (descriptor != null)
+ if (emailProviderDescriptor != null)
{
- services.Remove(descriptor);
+ services.Remove(emailProviderDescriptor);
}
services.AddScoped();
+
+ // Replace the real email service with mock for testing
+ var emailServiceDescriptor = services.SingleOrDefault(
+ d => d.ServiceType == typeof(IEmailService));
+
+ if (emailServiceDescriptor != null)
+ {
+ services.Remove(emailServiceDescriptor);
+ }
+
+ services.AddScoped();
});
}
}
diff --git a/src/Core/Core.slnx b/src/Core/Core.slnx
index a088dcd..4d6a6f1 100644
--- a/src/Core/Core.slnx
+++ b/src/Core/Core.slnx
@@ -21,6 +21,7 @@
+
diff --git a/src/Core/Service/Service.Auth.Tests/RegisterService.test.cs b/src/Core/Service/Service.Auth.Tests/RegisterService.test.cs
index b5d2675..d299785 100644
--- a/src/Core/Service/Service.Auth.Tests/RegisterService.test.cs
+++ b/src/Core/Service/Service.Auth.Tests/RegisterService.test.cs
@@ -4,6 +4,7 @@ using FluentAssertions;
using Infrastructure.PasswordHashing;
using Infrastructure.Repository.Auth;
using Moq;
+using Service.Emails;
namespace Service.Auth.Tests;
@@ -12,6 +13,7 @@ public class RegisterServiceTest
private readonly Mock _authRepoMock;
private readonly Mock _passwordInfraMock;
private readonly Mock _tokenServiceMock;
+ private readonly Mock _emailServiceMock; // todo handle email related test cases here
private readonly RegisterService _registerService;
public RegisterServiceTest()
@@ -19,11 +21,14 @@ public class RegisterServiceTest
_authRepoMock = new Mock();
_passwordInfraMock = new Mock();
_tokenServiceMock = new Mock();
+ _emailServiceMock = new Mock();
+
_registerService = new RegisterService(
_authRepoMock.Object,
_passwordInfraMock.Object,
- _tokenServiceMock.Object
+ _tokenServiceMock.Object,
+ _emailServiceMock.Object
);
}
@@ -92,6 +97,7 @@ public class RegisterServiceTest
.Setup(x => x.GenerateRefreshToken(It.IsAny()))
.Returns("refresh-token");
+
// Act
var result = await _registerService.RegisterAsync(
userAccount,
@@ -128,6 +134,8 @@ public class RegisterServiceTest
),
Times.Once
);
+ _emailServiceMock.Verify(x => x.SendRegistrationEmailAsync(It.IsAny(), It.IsAny()),
+ Times.Once);
}
[Fact]
@@ -307,4 +315,4 @@ public class RegisterServiceTest
Times.Once
);
}
-}
+}
\ No newline at end of file
diff --git a/src/Core/Service/Service.Auth/RegisterService.cs b/src/Core/Service/Service.Auth/RegisterService.cs
index f701a4e..8ba257e 100644
--- a/src/Core/Service/Service.Auth/RegisterService.cs
+++ b/src/Core/Service/Service.Auth/RegisterService.cs
@@ -4,13 +4,15 @@ using Infrastructure.Email;
using Infrastructure.Email.Templates.Rendering;
using Infrastructure.PasswordHashing;
using Infrastructure.Repository.Auth;
+using Service.Emails;
namespace Service.Auth;
public class RegisterService(
IAuthRepository authRepo,
IPasswordInfrastructure passwordInfrastructure,
- ITokenService tokenService
+ ITokenService tokenService,
+ IEmailService emailService
) : IRegisterService
{
private async Task ValidateUserDoesNotExist(UserAccount userAccount)
@@ -51,6 +53,9 @@ public class RegisterService(
var accessToken = tokenService.GenerateAccessToken(createdUser);
var refreshToken = tokenService.GenerateRefreshToken(createdUser);
+ // send confirmation email
+ await emailService.SendRegistrationEmailAsync(createdUser, "some-confirmation-token");
+
return new AuthServiceReturn(createdUser, refreshToken, accessToken);
}
}
diff --git a/src/Core/Service/Service.Auth/Service.Auth.csproj b/src/Core/Service/Service.Auth/Service.Auth.csproj
index 6b707d9..0057017 100644
--- a/src/Core/Service/Service.Auth/Service.Auth.csproj
+++ b/src/Core/Service/Service.Auth/Service.Auth.csproj
@@ -5,20 +5,18 @@
enable
-
-
-
-
-
-
+
+
-
-
+
+
+
diff --git a/src/Core/Service/Service.Emails/EmailService.cs b/src/Core/Service/Service.Emails/EmailService.cs
new file mode 100644
index 0000000..e73aa32
--- /dev/null
+++ b/src/Core/Service/Service.Emails/EmailService.cs
@@ -0,0 +1,32 @@
+using Domain.Entities;
+using Infrastructure.Email;
+using Infrastructure.Email.Templates.Rendering;
+
+namespace Service.Emails;
+
+public interface IEmailService
+{
+ public Task SendRegistrationEmailAsync(UserAccount createdUser, string confirmationToken);
+}
+
+public class EmailService(
+ IEmailProvider emailProvider,
+ IEmailTemplateProvider emailTemplateProvider) : IEmailService
+{
+ public async Task SendRegistrationEmailAsync(UserAccount createdUser, string confirmationToken)
+ {
+ var confirmationLink = $"https://thebiergarten.app/confirm?token={confirmationToken}";
+
+ var emailHtml = await emailTemplateProvider.RenderUserRegisteredEmailAsync(
+ createdUser.FirstName,
+ confirmationLink
+ );
+
+ await emailProvider.SendAsync(
+ createdUser.Email,
+ "Welcome to The Biergarten App!",
+ emailHtml,
+ isHtml: true
+ );
+ }
+}
diff --git a/src/Core/Service/Service.Emails/Service.Emails.csproj b/src/Core/Service/Service.Emails/Service.Emails.csproj
new file mode 100644
index 0000000..9e4d568
--- /dev/null
+++ b/src/Core/Service/Service.Emails/Service.Emails.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+