diff --git a/.github/workflows/createrelease.yml b/.github/workflows/createrelease.yml index a66501a..79c9f4b 100644 --- a/.github/workflows/createrelease.yml +++ b/.github/workflows/createrelease.yml @@ -37,14 +37,14 @@ jobs: dotnet test "MessagingService.EmailAggregate.Tests\MessagingService.EmailAggregate.Tests.csproj" dotnet test "MessagingService.Tests\MessagingService.Tests.csproj" - #- name: Build Docker Image - # run: docker build . --file MessagingService/Dockerfile --tag stuartferguson/messagingservice:latest --tag stuartferguson/messagingservice:${{ steps.get_version.outputs.VERSION }} + - name: Build Docker Image + run: docker build . --file MessagingService/Dockerfile --tag stuartferguson/messagingservice:latest --tag stuartferguson/messagingservice:${{ steps.get_version.outputs.VERSION }} - #- name: Publish Images to Docker Hub - # run: | - # docker login --username=${{ secrets.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }} - # docker push stuartferguson/messagingservice:latest - # docker push stuartferguson/messagingservice:${{ steps.get_version.outputs.VERSION }} + - name: Publish Images to Docker Hub + run: | + docker login --username=${{ secrets.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }} + docker push stuartferguson/messagingservice:latest + docker push stuartferguson/messagingservice:${{ steps.get_version.outputs.VERSION }} - name: Publish API run: dotnet publish "MessagingService\MessagingService.csproj" --configuration Release --output publishOutput diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index c79d84d..87a0966 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -34,8 +34,8 @@ jobs: dotnet test "MessagingService.EmailAggregate.Tests\MessagingService.EmailAggregate.Tests.csproj" dotnet test "MessagingService.Tests\MessagingService.Tests.csproj" - #- name: Build Docker Image - # run: docker build . --file MessagingService/Dockerfile --tag messagingservice:latest + - name: Build Docker Image + run: docker build . --file MessagingService/Dockerfile --tag messagingservice:latest - #- name: Run Integration Tests - # run: dotnet test "MessagingService.IntegrationTests\MessagingService.IntegrationTests.csproj" --filter Category=PRTest + - name: Run Integration Tests + run: dotnet test "MessagingService.IntegrationTests\MessagingService.IntegrationTests.csproj" --filter Category=PRTest diff --git a/MessagingService.IntegrationTests/Common/DockerHelper.cs b/MessagingService.IntegrationTests/Common/DockerHelper.cs new file mode 100644 index 0000000..15723aa --- /dev/null +++ b/MessagingService.IntegrationTests/Common/DockerHelper.cs @@ -0,0 +1,244 @@ +namespace MessagingService.IntegrationTests.Common +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Ductus.FluentDocker.Builders; + using Ductus.FluentDocker.Common; + using Ductus.FluentDocker.Model.Builders; + using Ductus.FluentDocker.Services; + using Ductus.FluentDocker.Services.Extensions; + using global::Shared.Logger; + using SecurityService.Client; + + /// + /// + /// + /// + public class DockerHelper : global::Shared.IntegrationTesting.DockerHelper + { + #region Fields + + /// + /// The security service client + /// + public ISecurityServiceClient SecurityServiceClient; + + public HttpClient MessagingServiceClient; + + /// + /// The test identifier + /// + public Guid TestId; + + /// + /// The containers + /// + protected List Containers; + + /// + /// The event store container name + /// + protected String EventStoreContainerName; + + /// + /// The event store HTTP port + /// + protected Int32 EventStoreHttpPort; + + /// + /// The security service container name + /// + protected String SecurityServiceContainerName; + + /// + /// The messaging service container name + /// + protected String MessagingServiceContainerName; + + /// + /// The security service port + /// + protected Int32 SecurityServicePort; + + /// + /// The messaging service port + /// + protected Int32 MessagingServicePort; + + /// + /// The test networks + /// + protected List TestNetworks; + + /// + /// The logger + /// + private readonly NlogLogger Logger; + + /// + /// The testing context + /// + private readonly TestingContext TestingContext; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The testing context. + public DockerHelper(NlogLogger logger, + TestingContext testingContext) + { + this.Logger = logger; + this.TestingContext = testingContext; + this.Containers = new List(); + this.TestNetworks = new List(); + } + + #endregion + + #region Methods + + /// + /// Starts the containers for scenario run. + /// + /// Name of the scenario. + public override async Task StartContainersForScenarioRun(String scenarioName) + { + String traceFolder = FdOs.IsWindows() ? $"D:\\home\\txnproc\\trace\\{scenarioName}" : $"//home//txnproc//trace//{scenarioName}"; + + Logging.Enabled(); + + Guid testGuid = Guid.NewGuid(); + this.TestId = testGuid; + + this.Logger.LogInformation($"Test Id is {testGuid}"); + + // Setup the container names + this.SecurityServiceContainerName = $"securityservice{testGuid:N}"; + this.EventStoreContainerName = $"eventstore{testGuid:N}"; + this.MessagingServiceContainerName = $"messagingservice{testGuid:N}"; + + (String, String, String) dockerCredentials = ("https://www.docker.com", "stuartferguson", "Sc0tland"); + + INetworkService testNetwork = DockerHelper.SetupTestNetwork(); + this.TestNetworks.Add(testNetwork); + IContainerService eventStoreContainer = + DockerHelper.SetupEventStoreContainer(this.EventStoreContainerName, this.Logger, "eventstore/eventstore:release-5.0.2", testNetwork, traceFolder); + + IContainerService securityServiceContainer = DockerHelper.SetupSecurityServiceContainer(this.SecurityServiceContainerName, + this.Logger, + "stuartferguson/securityservice", + testNetwork, + traceFolder, + dockerCredentials, + true); + + IContainerService messagingServiceContainer = DockerHelper.SetupMessagingServiceContainer(this.MessagingServiceContainerName, + this.Logger, + "messagingservice", + new List{ + testNetwork + }, + traceFolder, + dockerCredentials, + this.SecurityServiceContainerName, + this.EventStoreContainerName, + ("serviceClient", "Secret1")); + + this.Containers.AddRange(new List + { + eventStoreContainer, + securityServiceContainer, + messagingServiceContainer + }); + + // Cache the ports + this.SecurityServicePort = securityServiceContainer.ToHostExposedEndpoint("5001/tcp").Port; + this.EventStoreHttpPort = eventStoreContainer.ToHostExposedEndpoint("2113/tcp").Port; + this.MessagingServicePort = messagingServiceContainer.ToHostExposedEndpoint("5006/tcp").Port; + + // Setup the base address resolvers + String SecurityServiceBaseAddressResolver(String api) => $"http://127.0.0.1:{this.SecurityServicePort}"; + + HttpClient httpClient = new HttpClient(); + this.SecurityServiceClient = new SecurityServiceClient(SecurityServiceBaseAddressResolver, httpClient); + + this.MessagingServiceClient = new HttpClient(); + this.MessagingServiceClient.BaseAddress = new Uri($"http://127.0.0.1:{this.MessagingServicePort}"); + } + + /// + /// Stops the containers for scenario run. + /// + public override async Task StopContainersForScenarioRun() + { + if (this.Containers.Any()) + { + foreach (IContainerService containerService in this.Containers) + { + containerService.StopOnDispose = true; + containerService.RemoveOnDispose = true; + containerService.Dispose(); + } + } + + if (this.TestNetworks.Any()) + { + foreach (INetworkService networkService in this.TestNetworks) + { + networkService.Stop(); + networkService.Remove(true); + } + } + } + + public const int MessagingServiceDockerPort = 5006; + public static IContainerService SetupMessagingServiceContainer(String containerName, + ILogger logger, + String imageName, + List networkServices, + String hostFolder, + (String URL, String UserName, String Password)? dockerCredentials, + String securityServiceContainerName, + String eventStoreContainerName, + (String clientId, String clientSecret) clientDetails, + Boolean forceLatestImage = false, + Int32 securityServicePort = DockerHelper.SecurityServiceDockerPort) + { + logger.LogInformation("About to Start Messaging Service Container"); + + List environmentVariables = new List(); + environmentVariables + .Add($"EventStoreSettings:ConnectionString=ConnectTo=tcp://admin:changeit@{eventStoreContainerName}:{DockerHelper.EventStoreTcpDockerPort};VerboseLogging=true;"); + environmentVariables.Add($"AppSettings:SecurityService=http://{securityServiceContainerName}:{securityServicePort}"); + environmentVariables.Add($"SecurityConfiguration:Authority=http://{securityServiceContainerName}:{securityServicePort}"); + environmentVariables.Add($"urls=http://*:{DockerHelper.MessagingServiceDockerPort}"); + environmentVariables.Add("AppSettings:EmailProxy=Integration"); + + ContainerBuilder messagingServiceContainer = new Builder().UseContainer().WithName(containerName).WithEnvironment(environmentVariables.ToArray()) + .UseImage(imageName, forceLatestImage).ExposePort(DockerHelper.MessagingServiceDockerPort) + .UseNetwork(networkServices.ToArray()).Mount(hostFolder, "/home", MountType.ReadWrite); + + if (dockerCredentials.HasValue) + { + messagingServiceContainer.WithCredential(dockerCredentials.Value.URL, dockerCredentials.Value.UserName, dockerCredentials.Value.Password); + } + + // Now build and return the container + IContainerService builtContainer = messagingServiceContainer.Build().Start().WaitForPort($"{DockerHelper.MessagingServiceDockerPort}/tcp", 30000); + + logger.LogInformation("Messaging Service Container Started"); + + return builtContainer; + } + + #endregion + } +} \ No newline at end of file diff --git a/MessagingService.IntegrationTests/Common/GenericSteps.cs b/MessagingService.IntegrationTests/Common/GenericSteps.cs new file mode 100644 index 0000000..2278b22 --- /dev/null +++ b/MessagingService.IntegrationTests/Common/GenericSteps.cs @@ -0,0 +1,50 @@ +using NLog; +using Shared.Logger; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using TechTalk.SpecFlow; + +namespace MessagingService.IntegrationTests.Common +{ + [Binding] + [Scope(Tag = "base")] + public class GenericSteps + { + private readonly ScenarioContext ScenarioContext; + + private readonly TestingContext TestingContext; + + public GenericSteps(ScenarioContext scenarioContext, + TestingContext testingContext) + { + this.ScenarioContext = scenarioContext; + this.TestingContext = testingContext; + } + + [BeforeScenario] + public async Task StartSystem() + { + // Initialise a logger + String scenarioName = this.ScenarioContext.ScenarioInfo.Title.Replace(" ", ""); + NlogLogger logger = new NlogLogger(); + logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName); + LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); + + this.TestingContext.DockerHelper = new DockerHelper(logger, this.TestingContext); + this.TestingContext.Logger = logger; + this.TestingContext.Logger.LogInformation("About to Start Containers for Scenario Run"); + await this.TestingContext.DockerHelper.StartContainersForScenarioRun(scenarioName).ConfigureAwait(false); + this.TestingContext.Logger.LogInformation("Containers for Scenario Run Started"); + + await Task.Delay(TimeSpan.FromSeconds(20)); + } + + [AfterScenario] + public async Task StopSystem() + { + await this.TestingContext.DockerHelper.StopContainersForScenarioRun().ConfigureAwait(false); + } + } +} diff --git a/MessagingService.IntegrationTests/Common/SpecflowTableHelper.cs b/MessagingService.IntegrationTests/Common/SpecflowTableHelper.cs new file mode 100644 index 0000000..8b0ae2c --- /dev/null +++ b/MessagingService.IntegrationTests/Common/SpecflowTableHelper.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TechTalk.SpecFlow; + +namespace MessagingService.IntegrationTests.Common +{ + public static class SpecflowTableHelper + { + #region Methods + + public static Decimal GetDecimalValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return Decimal.TryParse(field, out Decimal value) ? value : -1; + } + + public static Boolean GetBooleanValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return Boolean.TryParse(field, out Boolean value) && value; + } + + public static Int32 GetIntValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return Int32.TryParse(field, out Int32 value) ? value : -1; + } + + public static Int16 GetShortValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + if (Int16.TryParse(field, out Int16 value)) + { + return value; + } + else + { + return -1; + } + } + + public static String GetStringRowValue(TableRow row, + String key) + { + return row.TryGetValue(key, out String value) ? value : ""; + } + + /// + /// Gets the date for date string. + /// + /// The date string. + /// The today. + /// + public static DateTime GetDateForDateString(String dateString, + DateTime today) + { + switch (dateString.ToUpper()) + { + case "TODAY": + return today.Date; + case "YESTERDAY": + return today.AddDays(-1).Date; + case "LASTWEEK": + return today.AddDays(-7).Date; + case "LASTMONTH": + return today.AddMonths(-1).Date; + case "LASTYEAR": + return today.AddYears(-1).Date; + case "TOMORROW": + return today.AddDays(1).Date; + default: + return DateTime.Parse(dateString); + } + } + + #endregion + } +} diff --git a/MessagingService.IntegrationTests/Common/TestingContext.cs b/MessagingService.IntegrationTests/Common/TestingContext.cs new file mode 100644 index 0000000..7e233c7 --- /dev/null +++ b/MessagingService.IntegrationTests/Common/TestingContext.cs @@ -0,0 +1,67 @@ +using Shared.Logger; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MessagingService.IntegrationTests.Common +{ + using System.Linq; + using DataTransferObjects; + using SecurityService.DataTransferObjects.Responses; + using Shouldly; + + public class TestingContext + { + public TestingContext() + { + this.Clients = new List(); + this.EmailResponses=new Dictionary(); + } + + public NlogLogger Logger { get; set; } + public String AccessToken { get; set; } + public DockerHelper DockerHelper { get; set; } + + private List Clients; + public void AddClientDetails(String clientId, + String clientSecret, + String grantType) + { + this.Clients.Add(ClientDetails.Create(clientId, clientSecret, grantType)); + } + + public ClientDetails GetClientDetails(String clientId) + { + ClientDetails clientDetails = this.Clients.SingleOrDefault(c => c.ClientId == clientId); + + clientDetails.ShouldNotBeNull(); + + return clientDetails; + } + + public Dictionary EmailResponses; + } + + public class ClientDetails + { + public String ClientId { get; private set; } + public String ClientSecret { get; private set; } + public String GrantType { get; private set; } + + private ClientDetails(String clientId, + String clientSecret, + String grantType) + { + this.ClientId = clientId; + this.ClientSecret = clientSecret; + this.GrantType = grantType; + } + + public static ClientDetails Create(String clientId, + String clientSecret, + String grantType) + { + return new ClientDetails(clientId, clientSecret, grantType); + } + } +} diff --git a/MessagingService.IntegrationTests/Email/SendEmail.feature b/MessagingService.IntegrationTests/Email/SendEmail.feature new file mode 100644 index 0000000..72d30bd --- /dev/null +++ b/MessagingService.IntegrationTests/Email/SendEmail.feature @@ -0,0 +1,22 @@ +@base @shared @email +Feature: SendEmail + +Background: + Given the following api resources exist + | ResourceName | DisplayName | Secret | Scopes | UserClaims | + | messagingService | Messaging REST | Secret1 | messagingService | | + + Given the following clients exist + | ClientId | ClientName | Secret | AllowedScopes | AllowedGrantTypes | + | serviceClient | Service Client | Secret1 | messagingService | client_credentials | + + Given I have a token to access the messaging service resource + | ClientId | + | serviceClient | + +@PRTest +Scenario: Send Email + Given I send the following Email Messages + | FromAddress | ToAddresses | Subject | Body | IsHtml | + | fromaddress@testemail.com | toaddress1@testemail.com | Test Email 1 | Test Body | true | + | fromaddress@testemail.com | toaddress1@testemail.com,toaddress2@testemail.com | Test Email 1 | Test Body | true | diff --git a/MessagingService.IntegrationTests/Email/SendEmail.feature.cs b/MessagingService.IntegrationTests/Email/SendEmail.feature.cs new file mode 100644 index 0000000..84b3cb7 --- /dev/null +++ b/MessagingService.IntegrationTests/Email/SendEmail.feature.cs @@ -0,0 +1,211 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:3.1.0.0 +// SpecFlow Generator Version:3.1.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace MessagingService.IntegrationTests.Email +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.1.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Xunit.TraitAttribute("Category", "base")] + [Xunit.TraitAttribute("Category", "shared")] + [Xunit.TraitAttribute("Category", "email")] + public partial class SendEmailFeature : object, Xunit.IClassFixture, System.IDisposable + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private string[] _featureTags = new string[] { + "base", + "shared", + "email"}; + + private Xunit.Abstractions.ITestOutputHelper _testOutputHelper; + +#line 1 "SendEmail.feature" +#line hidden + + public SendEmailFeature(SendEmailFeature.FixtureData fixtureData, MessagingService_IntegrationTests_XUnitAssemblyFixture assemblyFixture, Xunit.Abstractions.ITestOutputHelper testOutputHelper) + { + this._testOutputHelper = testOutputHelper; + this.TestInitialize(); + } + + public static void FeatureSetup() + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "SendEmail", null, ProgrammingLanguage.CSharp, new string[] { + "base", + "shared", + "email"}); + testRunner.OnFeatureStart(featureInfo); + } + + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + public virtual void TestInitialize() + { + } + + public virtual void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testOutputHelper); + } + + public virtual void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + public virtual void FeatureBackground() + { +#line 4 +#line hidden + TechTalk.SpecFlow.Table table1 = new TechTalk.SpecFlow.Table(new string[] { + "ResourceName", + "DisplayName", + "Secret", + "Scopes", + "UserClaims"}); + table1.AddRow(new string[] { + "messagingService", + "Messaging REST", + "Secret1", + "messagingService", + ""}); +#line 5 + testRunner.Given("the following api resources exist", ((string)(null)), table1, "Given "); +#line hidden + TechTalk.SpecFlow.Table table2 = new TechTalk.SpecFlow.Table(new string[] { + "ClientId", + "ClientName", + "Secret", + "AllowedScopes", + "AllowedGrantTypes"}); + table2.AddRow(new string[] { + "serviceClient", + "Service Client", + "Secret1", + "messagingService", + "client_credentials"}); +#line 9 + testRunner.Given("the following clients exist", ((string)(null)), table2, "Given "); +#line hidden + TechTalk.SpecFlow.Table table3 = new TechTalk.SpecFlow.Table(new string[] { + "ClientId"}); + table3.AddRow(new string[] { + "serviceClient"}); +#line 13 + testRunner.Given("I have a token to access the messaging service resource", ((string)(null)), table3, "Given "); +#line hidden + } + + void System.IDisposable.Dispose() + { + this.TestTearDown(); + } + + [Xunit.SkippableFactAttribute(DisplayName="Send Email")] + [Xunit.TraitAttribute("FeatureTitle", "SendEmail")] + [Xunit.TraitAttribute("Description", "Send Email")] + [Xunit.TraitAttribute("Category", "PRTest")] + public virtual void SendEmail() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Send Email", null, new string[] { + "PRTest"}); +#line 18 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden + TechTalk.SpecFlow.Table table4 = new TechTalk.SpecFlow.Table(new string[] { + "FromAddress", + "ToAddresses", + "Subject", + "Body", + "IsHtml"}); + table4.AddRow(new string[] { + "fromaddress@testemail.com", + "toaddress1@testemail.com", + "Test Email 1", + "Test Body", + "true"}); + table4.AddRow(new string[] { + "fromaddress@testemail.com", + "toaddress1@testemail.com,toaddress2@testemail.com", + "Test Email 1", + "Test Body", + "true"}); +#line 19 + testRunner.Given("I send the following Email Messages", ((string)(null)), table4, "Given "); +#line hidden + } + this.ScenarioCleanup(); + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.1.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class FixtureData : System.IDisposable + { + + public FixtureData() + { + SendEmailFeature.FeatureSetup(); + } + + void System.IDisposable.Dispose() + { + SendEmailFeature.FeatureTearDown(); + } + } + } +} +#pragma warning restore +#endregion diff --git a/MessagingService.IntegrationTests/Email/SendEmailSteps.cs b/MessagingService.IntegrationTests/Email/SendEmailSteps.cs new file mode 100644 index 0000000..b1677a7 --- /dev/null +++ b/MessagingService.IntegrationTests/Email/SendEmailSteps.cs @@ -0,0 +1,73 @@ +using System; +using TechTalk.SpecFlow; + +namespace MessagingService.IntegrationTests.Email +{ + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Common; + using DataTransferObjects; + using Newtonsoft.Json; + using Shouldly; + + [Binding] + [Scope(Tag = "email")] + public class SendEmailSteps + { + private readonly ScenarioContext ScenarioContext; + + private readonly TestingContext TestingContext; + + public SendEmailSteps(ScenarioContext scenarioContext, + TestingContext testingContext) + { + this.ScenarioContext = scenarioContext; + this.TestingContext = testingContext; + } + + [Given(@"I send the following Email Messages")] + public async Task GivenISendTheFollowingEmailMessages(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + await this.SendEmail(tableRow); + } + } + + private async Task SendEmail(TableRow tableRow) + { + String fromAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "FromAddress"); + String toAddresses = SpecflowTableHelper.GetStringRowValue(tableRow, "ToAddresses"); + String subject = SpecflowTableHelper.GetStringRowValue(tableRow, "Subject"); + String body = SpecflowTableHelper.GetStringRowValue(tableRow, "Body"); + Boolean isHtml = SpecflowTableHelper.GetBooleanValue(tableRow, "IsHtml"); + + SendEmailRequest request = new SendEmailRequest + { + Body = body, + ConnectionIdentifier = Guid.NewGuid(), + FromAddress = fromAddress, + IsHtml = isHtml, + Subject = subject, + ToAddresses = toAddresses.Split(",").ToList() + }; + + StringContent requestContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); + this.TestingContext.DockerHelper.MessagingServiceClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.TestingContext.AccessToken); + + HttpResponseMessage httpResponse = await this.TestingContext.DockerHelper.MessagingServiceClient.PostAsync("api/Email", requestContent, CancellationToken.None).ConfigureAwait(false); + + httpResponse.StatusCode.ShouldBe(HttpStatusCode.Created); + String responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + + responseContent.ShouldNotBeNullOrEmpty(); + SendEmailResponse response = JsonConvert.DeserializeObject(responseContent); + response.MessageId.ShouldNotBe(Guid.Empty); + } + } +} diff --git a/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj b/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj new file mode 100644 index 0000000..4979514 --- /dev/null +++ b/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj @@ -0,0 +1,47 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + SpecFlowSingleFileGenerator + SendEmail.feature.cs + + + + + + + True + + + + diff --git a/MessagingService.IntegrationTests/Shared/SharedSteps.cs b/MessagingService.IntegrationTests/Shared/SharedSteps.cs new file mode 100644 index 0000000..21c2b17 --- /dev/null +++ b/MessagingService.IntegrationTests/Shared/SharedSteps.cs @@ -0,0 +1,127 @@ +using MessagingService.IntegrationTests.Common; +using SecurityService.DataTransferObjects.Requests; +using SecurityService.DataTransferObjects.Responses; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TechTalk.SpecFlow; + +namespace MessagingService.IntegrationTests.Shared +{ + using SecurityService.DataTransferObjects; + using ClientDetails = Common.ClientDetails; + + [Binding] + [Scope(Tag = "shared")] + public class SharedSteps + { + private readonly ScenarioContext ScenarioContext; + + private readonly TestingContext TestingContext; + + public SharedSteps(ScenarioContext scenarioContext, + TestingContext testingContext) + { + this.ScenarioContext = scenarioContext; + this.TestingContext = testingContext; + } + + [Given(@"the following api resources exist")] + public async Task GivenTheFollowingApiResourcesExist(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String resourceName = SpecflowTableHelper.GetStringRowValue(tableRow, "ResourceName"); + String displayName = SpecflowTableHelper.GetStringRowValue(tableRow, "DisplayName"); + String secret = SpecflowTableHelper.GetStringRowValue(tableRow, "Secret"); + String scopes = SpecflowTableHelper.GetStringRowValue(tableRow, "Scopes"); + String userClaims = SpecflowTableHelper.GetStringRowValue(tableRow, "UserClaims"); + + List splitScopes = scopes.Split(",").ToList(); + List splitUserClaims = userClaims.Split(",").ToList(); + + CreateApiResourceRequest createApiResourceRequest = new CreateApiResourceRequest + { + Description = String.Empty, + DisplayName = displayName, + Name = resourceName, + Scopes = new List(), + Secret = secret, + UserClaims = new List() + }; + splitScopes.ForEach(a => + { + createApiResourceRequest.Scopes.Add(a.Trim()); + }); + splitUserClaims.ForEach(a => + { + createApiResourceRequest.UserClaims.Add(a.Trim()); + }); + + CreateApiResourceResponse createApiResourceResponse = await this.TestingContext.DockerHelper.SecurityServiceClient.CreateApiResource(createApiResourceRequest, CancellationToken.None).ConfigureAwait(false); + + createApiResourceResponse.ApiResourceName.ShouldBe(resourceName); + } + } + + [Given(@"the following clients exist")] + public async Task GivenTheFollowingClientsExist(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String clientId = SpecflowTableHelper.GetStringRowValue(tableRow, "ClientId"); + String clientName = SpecflowTableHelper.GetStringRowValue(tableRow, "ClientName"); + String secret = SpecflowTableHelper.GetStringRowValue(tableRow, "Secret"); + String allowedScopes = SpecflowTableHelper.GetStringRowValue(tableRow, "AllowedScopes"); + String allowedGrantTypes = SpecflowTableHelper.GetStringRowValue(tableRow, "AllowedGrantTypes"); + + List splitAllowedScopes = allowedScopes.Split(",").ToList(); + List splitAllowedGrantTypes = allowedGrantTypes.Split(",").ToList(); + + CreateClientRequest createClientRequest = new CreateClientRequest + { + Secret = secret, + AllowedGrantTypes = new List(), + AllowedScopes = new List(), + ClientDescription = String.Empty, + ClientId = clientId, + ClientName = clientName + }; + + splitAllowedScopes.ForEach(a => { createClientRequest.AllowedScopes.Add(a.Trim()); }); + splitAllowedGrantTypes.ForEach(a => { createClientRequest.AllowedGrantTypes.Add(a.Trim()); }); + + CreateClientResponse createClientResponse = await this.TestingContext.DockerHelper.SecurityServiceClient + .CreateClient(createClientRequest, CancellationToken.None).ConfigureAwait(false); + + createClientResponse.ClientId.ShouldBe(clientId); + + this.TestingContext.AddClientDetails(clientId, secret, allowedGrantTypes); + } + } + + [Given(@"I have a token to access the messaging service resource")] + public async Task GivenIHaveATokenToAccessTheMessagingServiceResource(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String clientId = SpecflowTableHelper.GetStringRowValue(tableRow, "ClientId"); + + ClientDetails clientDetails = this.TestingContext.GetClientDetails(clientId); + + if (clientDetails.GrantType == "client_credentials") + { + TokenResponse tokenResponse = await this.TestingContext.DockerHelper.SecurityServiceClient + .GetToken(clientId, clientDetails.ClientSecret, CancellationToken.None).ConfigureAwait(false); + + this.TestingContext.AccessToken = tokenResponse.AccessToken; + } + } + } + + } +} diff --git a/MessagingService.Tests/General/BootstrapperTests.cs b/MessagingService.Tests/General/BootstrapperTests.cs index 56cfb30..fb08480 100644 --- a/MessagingService.Tests/General/BootstrapperTests.cs +++ b/MessagingService.Tests/General/BootstrapperTests.cs @@ -52,6 +52,7 @@ private IConfigurationRoot SetupMemoryConfiguration() configuration.Add("AppSettings:ClientSecret", "clientSecret"); configuration.Add("AppSettings:EstateManagementApi", "http://localhost"); configuration.Add("AppSettings:SecurityService", "http://localhost"); + configuration.Add("AppSettings:EmailProxy", "UnitTest"); builder.AddInMemoryCollection(configuration); diff --git a/MessagingService.sln b/MessagingService.sln index 95a8a12..caa08cd 100644 --- a/MessagingService.sln +++ b/MessagingService.sln @@ -17,13 +17,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.EmailMessa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.EmailMessage.DomainEvents", "MessagingService.EmailMessage.DomainEvents\MessagingService.EmailMessage.DomainEvents.csproj", "{689A531D-86EB-4656-81B3-4C6E569A7E4B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.Tests", "MessagingService.Tests\MessagingService.Tests.csproj", "{1617DC31-CE88-49ED-A865-CE896E8D861D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.Tests", "MessagingService.Tests\MessagingService.Tests.csproj", "{1617DC31-CE88-49ED-A865-CE896E8D861D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.Testing", "MessagingService.Testing\MessagingService.Testing.csproj", "{5CEAAA6F-2B2C-4C1F-B812-65E5ACC9002C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.Testing", "MessagingService.Testing\MessagingService.Testing.csproj", "{5CEAAA6F-2B2C-4C1F-B812-65E5ACC9002C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.BusinessLogic.Tests", "MessagingService.BusinessLogic.Tests\MessagingService.BusinessLogic.Tests.csproj", "{17A755D6-96EE-46EB-8850-9641B8F1A5E1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.BusinessLogic.Tests", "MessagingService.BusinessLogic.Tests\MessagingService.BusinessLogic.Tests.csproj", "{17A755D6-96EE-46EB-8850-9641B8F1A5E1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.EmailAggregate.Tests", "MessagingService.EmailAggregate.Tests\MessagingService.EmailAggregate.Tests.csproj", "{2FBED4B6-3096-4AD1-8436-247A59E0CDC2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.EmailAggregate.Tests", "MessagingService.EmailAggregate.Tests\MessagingService.EmailAggregate.Tests.csproj", "{2FBED4B6-3096-4AD1-8436-247A59E0CDC2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.IntegrationTests", "MessagingService.IntegrationTests\MessagingService.IntegrationTests.csproj", "{FEBD44D2-5B93-40D8-B59C-E5221570055D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -67,6 +69,10 @@ Global {2FBED4B6-3096-4AD1-8436-247A59E0CDC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {2FBED4B6-3096-4AD1-8436-247A59E0CDC2}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FBED4B6-3096-4AD1-8436-247A59E0CDC2}.Release|Any CPU.Build.0 = Release|Any CPU + {FEBD44D2-5B93-40D8-B59C-E5221570055D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEBD44D2-5B93-40D8-B59C-E5221570055D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEBD44D2-5B93-40D8-B59C-E5221570055D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEBD44D2-5B93-40D8-B59C-E5221570055D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,6 +87,7 @@ Global {5CEAAA6F-2B2C-4C1F-B812-65E5ACC9002C} = {9AEE6ADE-DD45-4605-A933-E06CF0BA4203} {17A755D6-96EE-46EB-8850-9641B8F1A5E1} = {9AEE6ADE-DD45-4605-A933-E06CF0BA4203} {2FBED4B6-3096-4AD1-8436-247A59E0CDC2} = {9AEE6ADE-DD45-4605-A933-E06CF0BA4203} + {FEBD44D2-5B93-40D8-B59C-E5221570055D} = {9AEE6ADE-DD45-4605-A933-E06CF0BA4203} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1929C0FE-8CEB-4D0E-BD22-9E5E16E2B49F} diff --git a/MessagingService/Dockerfile b/MessagingService/Dockerfile index fe4b032..8e9c518 100644 --- a/MessagingService/Dockerfile +++ b/MessagingService/Dockerfile @@ -2,11 +2,15 @@ FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base WORKDIR /app -EXPOSE 80 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build WORKDIR /src +COPY ["MessagingService/NuGet.Config", "."] COPY ["MessagingService/MessagingService.csproj", "MessagingService/"] +COPY ["MessagingService.BusinessLogic/MessagingService.BusinessLogic.csproj", "MessagingService.BusinessLogic/"] +COPY ["MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj", "MessagingService.EmailMessageAggregate/"] +COPY ["MessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj", "MessagingService.EmailMessage.DomainEvents/"] +COPY ["MessagingService.DataTransferObjects/MessagingService.DataTransferObjects.csproj", "MessagingService.DataTransferObjects/"] RUN dotnet restore "MessagingService/MessagingService.csproj" COPY . . WORKDIR "/src/MessagingService" diff --git a/MessagingService/NuGet.Config b/MessagingService/NuGet.Config new file mode 100644 index 0000000..c6bc4ff --- /dev/null +++ b/MessagingService/NuGet.Config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MessagingService/Startup.cs b/MessagingService/Startup.cs index b2cbd66..bf978dc 100644 --- a/MessagingService/Startup.cs +++ b/MessagingService/Startup.cs @@ -33,6 +33,7 @@ namespace MessagingService using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using NLog.Extensions.Logging; + using Service.Services.Email.IntegrationTest; using Shared.DomainDrivenDesign.EventStore; using Shared.EntityFramework.ConnectionStringConfiguration; using Shared.EventStore.EventStore; @@ -129,7 +130,9 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton, AggregateRepository>(); services.AddSingleton(); - services.AddSingleton(); + + this.RegisterEmailProxy(services); + //services.AddSingleton(); //services.AddSingleton(); //services.AddSingleton(); @@ -149,6 +152,25 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); } + /// + /// Registers the email proxy. + /// + /// The services. + private void RegisterEmailProxy(IServiceCollection services) + { + // read the config setting + String emailProxy = ConfigurationReader.GetValue("AppSettings", "EmailProxy"); + + if (emailProxy == "Smtp2Go") + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } + } + private void ConfigureMiddlewareServices(IServiceCollection services) { services.AddApiVersioning( diff --git a/MessagingService/appsettings.json b/MessagingService/appsettings.json index 0935e8a..746da91 100644 --- a/MessagingService/appsettings.json +++ b/MessagingService/appsettings.json @@ -20,6 +20,7 @@ "AppSettings": { "UseConnectionStringConfig": false, "SecurityService": "http://192.168.1.133:5001", + "EmailProxy": "Smtp2Go", "SMTP2GoBaseAddress": "https://api.smtp2go.com/v3/", "SMTP2GoAPIKey": "api-4CE2C6BC80D111EAB45BF23C91C88F4E" },