diff --git a/Distribt.sln b/Distribt.sln index 7274e8a..eea99fc 100644 --- a/Distribt.sln +++ b/Distribt.sln @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Subscriptions", "Subscripti EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Services.Emails", "src\Services\Emails\Distribt.Services.Emails\Distribt.Services.Emails.csproj", "{7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Services.Emails.Application", "src\Services\Emails\Distribt.Services.Emails.Application\Distribt.Services.Emails.Application.csproj", "{CD5AC732-1DDE-44F7-8F06-C4D829CD124E}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Services.Orders", "src\Services\Orders\Distribt.Services.Orders\Distribt.Services.Orders.csproj", "{EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Services.Products.Api.Write", "src\Services\Products\Distribt.Services.Products.Api.Write\Distribt.Services.Products.Api.Write.csproj", "{84E52341-0706-460A-8715-0DEC8E62ECD5}" @@ -65,6 +67,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{10 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Subscriptions", "Subscriptions", "{1A3349DA-38FB-4461-AA06-E7ACB2D4AD20}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Emails", "Emails", "{83F8AA6C-6C0B-457D-AD8D-2B9D284CC7B6}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Tests.Services.Subscriptions.ApiTests", "src\Tests\Services\Subscriptions\Distribt.Tests.Services.Subscriptions.ApiTests\Distribt.Tests.Services.Subscriptions.ApiTests.csproj", "{59693C3A-C300-4B18-899C-02F3F5E60B90}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Services.Products.Api.Read", "src\Services\Products\Distribt.Services.Products.Api.Read\Distribt.Services.Products.Api.Read.csproj", "{407D987E-A900-4BDC-8692-9F88F277A4EF}" @@ -93,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Discovery", "Discovery", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Distribt.Test.Shared.Discovery.Tests", "src\Tests\Shared\Discovery\Distribt.Test.Shared.Discovery.Tests\Distribt.Test.Shared.Discovery.Tests.csproj", "{F9C0F635-D488-4E1F-A4AA-912EA8C518B5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Distribt.Tests.Services.Emails.ApplicationTests", "src\Tests\Services\Emails\Distribt.Tests.Services.Emails.ApplicationTests\Distribt.Tests.Services.Emails.ApplicationTests.csproj", "{0E5FDB89-9713-44B1-89E6-CBCD36A085AB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -110,8 +116,12 @@ Global {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}.Release|Any CPU.Build.0 = Release|Any CPU - {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18}.Release|Any CPU.Build.0 = Release|Any CPU + {CD5AC732-1DDE-44F7-8F06-C4D829CD124E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD5AC732-1DDE-44F7-8F06-C4D829CD124E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD5AC732-1DDE-44F7-8F06-C4D829CD124E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD5AC732-1DDE-44F7-8F06-C4D829CD124E}.Release|Any CPU.Build.0 = Release|Any CPU + {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1}.Release|Any CPU.Build.0 = Release|Any CPU @@ -207,11 +217,15 @@ Global {E3AA00A8-ED12-454B-A32B-86DDDEB0B3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3AA00A8-ED12-454B-A32B-86DDDEB0B3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3AA00A8-ED12-454B-A32B-86DDDEB0B3C5}.Release|Any CPU.Build.0 = Release|Any CPU - {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9C0F635-D488-4E1F-A4AA-912EA8C518B5}.Release|Any CPU.Build.0 = Release|Any CPU + {0E5FDB89-9713-44B1-89E6-CBCD36A085AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E5FDB89-9713-44B1-89E6-CBCD36A085AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E5FDB89-9713-44B1-89E6-CBCD36A085AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E5FDB89-9713-44B1-89E6-CBCD36A085AB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection @@ -224,7 +238,8 @@ Global {5CA04AE7-BB29-4752-83E3-DA1ACAC05504} = {A086B49C-F6C6-4AF1-B7F1-6A16ECA538BE} {B3869489-0DB3-4AA4-8B5F-DC5E54499AA8} = {A086B49C-F6C6-4AF1-B7F1-6A16ECA538BE} {ED38326B-E44E-4CC1-93FE-0D75B35C1735} = {A086B49C-F6C6-4AF1-B7F1-6A16ECA538BE} - {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18} = {986E19E7-E3FF-4707-958D-17610C75D7E8} + {7C9FA796-6B84-4EB5-9F24-8E7C4408DC18} = {986E19E7-E3FF-4707-958D-17610C75D7E8} + {CD5AC732-1DDE-44F7-8F06-C4D829CD124E} = {986E19E7-E3FF-4707-958D-17610C75D7E8} {EEF25E04-0BB7-4951-A1EC-EF54AF03E7B1} = {5CA04AE7-BB29-4752-83E3-DA1ACAC05504} {84E52341-0706-460A-8715-0DEC8E62ECD5} = {B3869489-0DB3-4AA4-8B5F-DC5E54499AA8} {4A4A95E8-9DBF-4EC7-AF48-AF4F5B84567C} = {ED38326B-E44E-4CC1-93FE-0D75B35C1735} @@ -252,13 +267,15 @@ Global {86ED7F06-EB4E-403A-82F0-56EF5D029BBC} = {E93B860C-F098-4DBD-8C28-B7A5B3E73D20} {8858633A-CCE1-423F-9FFD-3CB7A6354ED6} = {CE3861D4-4B49-43A7-ADCD-BF7D076E9F9D} {2C8756C1-2FD0-42C6-A13A-0675B9BFEC37} = {5CA04AE7-BB29-4752-83E3-DA1ACAC05504} - {6B7F9261-46BD-4095-87A6-CDAF806A7375} = {102C446D-0625-49A7-B4D9-F606924E98F6} - {E3AA00A8-ED12-454B-A32B-86DDDEB0B3C5} = {6B7F9261-46BD-4095-87A6-CDAF806A7375} - {350872D7-1998-4646-B0B5-B103F702E3F6} = {98B9BF53-DD00-441A-A22E-76607782742E} - {547BD8CA-B96D-4D23-B54C-9BC828465FF7} = {350872D7-1998-4646-B0B5-B103F702E3F6} - {F9C0F635-D488-4E1F-A4AA-912EA8C518B5} = {547BD8CA-B96D-4D23-B54C-9BC828465FF7} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution + {6B7F9261-46BD-4095-87A6-CDAF806A7375} = {102C446D-0625-49A7-B4D9-F606924E98F6} + {E3AA00A8-ED12-454B-A32B-86DDDEB0B3C5} = {6B7F9261-46BD-4095-87A6-CDAF806A7375} + {350872D7-1998-4646-B0B5-B103F702E3F6} = {98B9BF53-DD00-441A-A22E-76607782742E} + {547BD8CA-B96D-4D23-B54C-9BC828465FF7} = {350872D7-1998-4646-B0B5-B103F702E3F6} + {F9C0F635-D488-4E1F-A4AA-912EA8C518B5} = {547BD8CA-B96D-4D23-B54C-9BC828465FF7} + {83F8AA6C-6C0B-457D-AD8D-2B9D284CC7B6} = {102C446D-0625-49A7-B4D9-F606924E98F6} + {0E5FDB89-9713-44B1-89E6-CBCD36A085AB} = {83F8AA6C-6C0B-457D-AD8D-2B9D284CC7B6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3532EB9C-D36A-4A7D-9494-BBC170208D46} EndGlobalSection EndGlobal diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailEntity.cs b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailEntity.cs new file mode 100644 index 0000000..1a60104 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailEntity.cs @@ -0,0 +1,23 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Distribt.Services.Emails.Application.DataAccess; + +internal class EmailEntity +{ + [BsonId] + public ObjectId _id { get; set; } = ObjectId.GenerateNewId(); + public string Id { get; set; } = default!; + public string From { get; set; } = default!; + public string To { get; set; } = default!; + public string Subject { get; set; } = default!; + public string Body { get; set; } = default!; + [BsonRepresentation(BsonType.String)] + public EmailStatus Status { get; set; } = EmailStatus.Received; +} + +public enum EmailStatus +{ + Received, + Sent +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepository.cs b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepository.cs new file mode 100644 index 0000000..0d5d36e --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepository.cs @@ -0,0 +1,53 @@ +using Distribt.Services.Emails.Controllers; +using Distribt.Shared.Databases.MongoDb; +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace Distribt.Services.Emails.Application.DataAccess; + +public interface IEmailRepository +{ + Task AddEmail(EmailDto email, CancellationToken cancellationToken = default); + Task MarkAsSent(string id, CancellationToken cancellationToken = default); +} + +public class EmailRepository : IEmailRepository +{ + private readonly MongoClient _client; + private readonly IMongoDatabase _database; + private readonly EmailRepositoryConfiguration _configuration; + + public EmailRepository(IMongoDbConnectionProvider mongoDbConnectionProvider, + IOptions databaseConfiguration, + IOptions repositoryConfiguration) + { + _client = new MongoClient(mongoDbConnectionProvider.GetMongoUrl()); + _database = _client.GetDatabase(databaseConfiguration.Value.DatabaseName); + _configuration = repositoryConfiguration.Value; + } + + public async Task AddEmail(EmailDto email, CancellationToken cancellationToken = default) + { + IMongoCollection collection = _database.GetCollection(_configuration.CollectionName); + EmailEntity entity = new EmailEntity + { + Id = Guid.NewGuid().ToString(), + From = email.from, + To = email.to, + Subject = email.subject, + Body = email.body, + Status = EmailStatus.Received + }; + await collection.InsertOneAsync(entity, cancellationToken: cancellationToken); + return entity.Id; + } + + public async Task MarkAsSent(string id, CancellationToken cancellationToken = default) + { + IMongoCollection collection = _database.GetCollection(_configuration.CollectionName); + FilterDefinition filter = Builders.Filter.Eq("Id", id); + UpdateDefinition update = Builders.Update.Set(e => e.Status, EmailStatus.Sent); + await collection.UpdateOneAsync(filter, update, cancellationToken: cancellationToken); + } + +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepositoryConfiguration.cs b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepositoryConfiguration.cs new file mode 100644 index 0000000..c4dd9fb --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/DataAccess/EmailRepositoryConfiguration.cs @@ -0,0 +1,6 @@ +namespace Distribt.Services.Emails.Application.DataAccess; + +public class EmailRepositoryConfiguration +{ + public string CollectionName { get; set; } = "Emails"; +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/Distribt.Services.Emails.Application.csproj b/src/Services/Emails/Distribt.Services.Emails.Application/Distribt.Services.Emails.Application.csproj new file mode 100644 index 0000000..7e427d5 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/Distribt.Services.Emails.Application.csproj @@ -0,0 +1,12 @@ + + + net8.0 + enable + enable + true + Distribt.Services.Emails.Application + + + + + diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/Email/EmailUseCases.cs b/src/Services/Emails/Distribt.Services.Emails.Application/Email/EmailUseCases.cs new file mode 100644 index 0000000..ea68742 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/Email/EmailUseCases.cs @@ -0,0 +1,11 @@ +namespace Distribt.Services.Emails.Application.Email; + +public class EmailUseCases +{ + public ISendEmailUseCase SendEmail { get; } + + public EmailUseCases(ISendEmailUseCase sendEmailUseCase) + { + SendEmail = sendEmailUseCase; + } +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/Email/SendEmailUseCase.cs b/src/Services/Emails/Distribt.Services.Emails.Application/Email/SendEmailUseCase.cs new file mode 100644 index 0000000..4c01ad6 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/Email/SendEmailUseCase.cs @@ -0,0 +1,29 @@ +using Distribt.Services.Emails.Application.DataAccess; +using Distribt.Services.Emails.Controllers; + +namespace Distribt.Services.Emails.Application.Email; + +public interface ISendEmailUseCase +{ + Task Execute(EmailDto emailDto); +} + +public class SendEmailUseCase : ISendEmailUseCase +{ + private readonly IEmailRepository _repository; + private readonly IEmailProvider _provider; + + public SendEmailUseCase(IEmailRepository repository, IEmailProvider provider) + { + _repository = repository; + _provider = provider; + } + + public async Task Execute(EmailDto emailDto) + { + string id = await _repository.AddEmail(emailDto); + await _provider.Send(emailDto); + await _repository.MarkAsSent(id); + return true; + } +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/EmailsApplicationDependencyInjection.cs b/src/Services/Emails/Distribt.Services.Emails.Application/EmailsApplicationDependencyInjection.cs new file mode 100644 index 0000000..5ad98f5 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/EmailsApplicationDependencyInjection.cs @@ -0,0 +1,22 @@ +using Distribt.Services.Emails.Application.DataAccess; +using Distribt.Services.Emails.Application.Email; +using Distribt.Services.Emails.Application.Services; +using Distribt.Shared.Setup.Databases; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Distribt.Services.Emails.Application; + +public static class EmailsApplicationDependencyInjection +{ + public static void AddEmailsApplication(this IServiceCollection services, IConfiguration configuration) + { + services.AddDistribtMongoDbConnectionProvider(configuration); + services.Configure(configuration.GetSection("EmailRepository")); + + services.AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); + } +} diff --git a/src/Services/Emails/Distribt.Services.Emails.Application/Services/EmailProvider.cs b/src/Services/Emails/Distribt.Services.Emails.Application/Services/EmailProvider.cs new file mode 100644 index 0000000..dcfd221 --- /dev/null +++ b/src/Services/Emails/Distribt.Services.Emails.Application/Services/EmailProvider.cs @@ -0,0 +1,17 @@ +using Distribt.Services.Emails.Controllers; + +namespace Distribt.Services.Emails.Application.Services; + +public interface IEmailProvider +{ + Task Send(EmailDto email, CancellationToken cancellationToken = default); +} + +public class FakeEmailProvider : IEmailProvider +{ + public Task Send(EmailDto email, CancellationToken cancellationToken = default) + { + // Fake provider does nothing + return Task.CompletedTask; + } +} diff --git a/src/Services/Emails/Distribt.Services.Emails/Controllers/EmailController.cs b/src/Services/Emails/Distribt.Services.Emails/Controllers/EmailController.cs index de5b4f0..5ee4e16 100644 --- a/src/Services/Emails/Distribt.Services.Emails/Controllers/EmailController.cs +++ b/src/Services/Emails/Distribt.Services.Emails/Controllers/EmailController.cs @@ -1,17 +1,25 @@ using Microsoft.AspNetCore.Mvc; +using Distribt.Services.Emails.Application.Email; namespace Distribt.Services.Emails.Controllers; [ApiController] [Route("[controller]")] public class EmailController { + private readonly EmailUseCases _emailUseCases; + + public EmailController(EmailUseCases emailUseCases) + { + _emailUseCases = emailUseCases; + } + [HttpPost(Name = "send")] public Task Send(EmailDto emailDto) { - //TODO: logic to send the email. - return Task.FromResult(true); + return _emailUseCases.SendEmail.Execute(emailDto); } } public record EmailDto(string from, string to, string subject, string body); + diff --git a/src/Services/Emails/Distribt.Services.Emails/Distribt.Services.Emails.csproj b/src/Services/Emails/Distribt.Services.Emails/Distribt.Services.Emails.csproj index e4af74e..f1a1979 100644 --- a/src/Services/Emails/Distribt.Services.Emails/Distribt.Services.Emails.csproj +++ b/src/Services/Emails/Distribt.Services.Emails/Distribt.Services.Emails.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Services/Emails/Distribt.Services.Emails/Program.cs b/src/Services/Emails/Distribt.Services.Emails/Program.cs index 9eb34d8..2d67774 100644 --- a/src/Services/Emails/Distribt.Services.Emails/Program.cs +++ b/src/Services/Emails/Distribt.Services.Emails/Program.cs @@ -1,3 +1,8 @@ -WebApplication app = DefaultDistribtWebApplication.Create(args); +using Distribt.Services.Emails.Application; -DefaultDistribtWebApplication.Run(app); \ No newline at end of file +WebApplication app = DefaultDistribtWebApplication.Create(args, builder => +{ + builder.Services.AddEmailsApplication(builder.Configuration); +}); + +DefaultDistribtWebApplication.Run(app); diff --git a/src/Services/Emails/Distribt.Services.Emails/appsettings.json b/src/Services/Emails/Distribt.Services.Emails/appsettings.json index 6af346b..5405f85 100644 --- a/src/Services/Emails/Distribt.Services.Emails/appsettings.json +++ b/src/Services/Emails/Distribt.Services.Emails/appsettings.json @@ -9,5 +9,13 @@ "Discovery": { "Address": "http://localhost:8500" }, + "Database": { + "MongoDb": { + "DatabaseName": "distribt" + } + }, + "EmailRepository": { + "CollectionName": "Emails" + }, "AllowedHosts": "*" } diff --git a/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/Distribt.Tests.Services.Emails.ApplicationTests.csproj b/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/Distribt.Tests.Services.Emails.ApplicationTests.csproj new file mode 100644 index 0000000..5ec4787 --- /dev/null +++ b/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/Distribt.Tests.Services.Emails.ApplicationTests.csproj @@ -0,0 +1,24 @@ + + + net8.0 + enable + false + Distribt.Tests.Services.Emails.Application + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/UseCases/SendEmailUseCaseTest.cs b/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/UseCases/SendEmailUseCaseTest.cs new file mode 100644 index 0000000..02823b0 --- /dev/null +++ b/src/Tests/Services/Emails/Distribt.Tests.Services.Emails.ApplicationTests/UseCases/SendEmailUseCaseTest.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using Distribt.Services.Emails.Application.DataAccess; +using Distribt.Services.Emails.Application.Email; +using Distribt.Services.Emails.Application.Services; +using Distribt.Services.Emails.Controllers; +using Moq; +using Xunit; + +namespace Distribt.Tests.Services.Emails.Application.UseCases; + +public class SendEmailUseCaseTest +{ + [Fact] + public async Task WhenExecute_ThenStoresAndSendsEmail() + { + var repository = new Mock(); + var provider = new Mock(); + repository.Setup(r => r.AddEmail(It.IsAny(), It.IsAny())) + .ReturnsAsync("1"); + + ISendEmailUseCase useCase = new SendEmailUseCase(repository.Object, provider.Object); + EmailDto dto = new("from", "to", "subject", "body"); + + bool result = await useCase.Execute(dto); + + Assert.True(result); + repository.Verify(r => r.AddEmail(dto, It.IsAny()), Times.Once); + provider.Verify(p => p.Send(dto, It.IsAny()), Times.Once); + repository.Verify(r => r.MarkAsSent("1", It.IsAny()), Times.Once); + } +}