From f54ed0b5fed3b937475592959819aaa0c216268f Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Sun, 8 Mar 2020 15:59:31 +0000 Subject: [PATCH] Fix nightly build --- .../Services/TransactionDomainService.cs | 15 +- .../TransactionProcessor.BusinessLogic.csproj | 10 +- .../TransactionProcessor.Client.csproj | 2 +- .../Common/DockerHelper.cs | 217 +++++++++++++++--- .../Common/GenericSteps.cs | 2 +- .../Common/Setup.cs | 37 ++- .../Common/TestingContext.cs | 4 + .../Shared/SharedSteps.cs | 8 +- ...ansactionProcessor.IntegrationTests.csproj | 11 +- ...nProcessor.Transaction.DomainEvents.csproj | 2 +- ...ctionProcessor.TransactionAggregate.csproj | 2 +- TransactionProcessor/Startup.cs | 5 +- .../TransactionProcessor.csproj | 2 +- 13 files changed, 258 insertions(+), 59 deletions(-) diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index ae3b76c8..54e42e64 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -13,6 +13,7 @@ using SecurityService.DataTransferObjects.Responses; using Shared.DomainDrivenDesign.EventStore; using Shared.EventStore.EventStore; + using Shared.Exceptions; using Shared.General; using Shared.Logger; using TransactionAggregate; @@ -147,15 +148,17 @@ public async Task ProcessLogonTransaction(Guid TokenResponse token = await this.SecurityServiceClient.GetToken(clientId, clientSecret, cancellationToken); Logger.LogInformation($"Token is {token.AccessToken}"); - // Validate the Estate Record is a valid estate - var estate = await this.EstateClient.GetEstate(token.AccessToken, estateId, cancellationToken); - - // TODO: Remove this once GetEstate returns correct response when estate not found - if (estate.EstateName == null) + EstateResponse estate = null; + try + { + // Validate the Estate Record is a valid estate + estate = await this.EstateClient.GetEstate(token.AccessToken, estateId, cancellationToken); + } + catch(Exception e) when (e.InnerException is KeyNotFoundException) { throw new TransactionValidationException($"Estate Id [{estateId}] is not a valid estate", TransactionResponseCode.InvalidEstateId); } - + // get the merchant record and validate the device // TODO: Token MerchantResponse merchant = await this.EstateClient.GetMerchant(token.AccessToken, estateId, merchantId, cancellationToken); diff --git a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj index b309e542..f7e06e29 100644 --- a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj +++ b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj @@ -5,11 +5,11 @@ - - - - - + + + + + diff --git a/TransactionProcessor.Client/TransactionProcessor.Client.csproj b/TransactionProcessor.Client/TransactionProcessor.Client.csproj index 08cd2d8f..cff00d63 100644 --- a/TransactionProcessor.Client/TransactionProcessor.Client.csproj +++ b/TransactionProcessor.Client/TransactionProcessor.Client.csproj @@ -6,7 +6,7 @@ - + diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs index 09bb2efc..e5c09d1b 100644 --- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -2,15 +2,19 @@ { using System; using System.Collections.Generic; + using System.Data; using System.Linq; using System.Net.Http; + using System.Threading; using System.Threading.Tasks; using Client; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; using EstateManagement.Client; + using EstateReporting.Database; using global::Shared.Logger; + using Microsoft.Data.SqlClient; using SecurityService.Client; /// @@ -66,6 +70,18 @@ public class DockerHelper : global::Shared.IntegrationTesting.DockerHelper /// protected List TestNetworks; + protected String SecurityServiceContainerName; + + protected String EstateManagementContainerName; + + protected String EventStoreContainerName; + + protected String EstateReportingContainerName; + + protected String SubscriptionServiceContainerName; + + protected String TransactionProcessorContainerName; + /// /// The transaction processor port /// @@ -76,17 +92,21 @@ public class DockerHelper : global::Shared.IntegrationTesting.DockerHelper /// private readonly NlogLogger Logger; + private readonly TestingContext TestingContext; + #endregion #region Constructors /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The logger. - public DockerHelper(NlogLogger logger) + /// The testing context. + public DockerHelper(NlogLogger logger, TestingContext testingContext) { this.Logger = logger; + this.TestingContext = testingContext; this.Containers = new List(); this.TestNetworks = new List(); } @@ -109,35 +129,36 @@ public override async Task StartContainersForScenarioRun(String scenarioName) this.TestId = testGuid; this.Logger.LogInformation($"Test Id is {testGuid}"); - + // Setup the container names - String securityServiceContainerName = $"securityservice{testGuid:N}"; - String estateManagementApiContainerName = $"estate{testGuid:N}"; - String transactionProcessorContainerName = $"txnprocessor{testGuid:N}"; - String eventStoreContainerName = $"eventstore{testGuid:N}"; + this.SecurityServiceContainerName = $"securityservice{testGuid:N}"; + this.EstateManagementContainerName = $"estate{testGuid:N}"; + this.EventStoreContainerName = $"eventstore{testGuid:N}"; + this.EstateReportingContainerName = $"estatereporting{testGuid:N}"; + this.SubscriptionServiceContainerName = $"subscription{testGuid:N}"; + this.TransactionProcessorContainerName = $"txnprocessor{testGuid:N}"; (String, String, String) dockerCredentials = ("https://www.docker.com", "stuartferguson", "Sc0tland"); INetworkService testNetwork = DockerHelper.SetupTestNetwork(); this.TestNetworks.Add(testNetwork); IContainerService eventStoreContainer = - DockerHelper.SetupEventStoreContainer(eventStoreContainerName, this.Logger, "eventstore/eventstore:release-5.0.2", testNetwork, traceFolder); - - IContainerService estateManagementContainer = DockerHelper.SetupEstateManagementContainer(estateManagementApiContainerName, - this.Logger, - "stuartferguson/estatemanagement", - new List - { - testNetwork - }, - traceFolder, - dockerCredentials, - securityServiceContainerName, - eventStoreContainerName, - (null, null), - true); - - IContainerService securityServiceContainer = DockerHelper.SetupSecurityServiceContainer(securityServiceContainerName, + DockerHelper.SetupEventStoreContainer(this.EventStoreContainerName, this.Logger, "eventstore/eventstore:release-5.0.2", testNetwork, traceFolder); + + IContainerService estateManagementContainer = DockerHelper.SetupEstateManagementContainer(this.EstateManagementContainerName, this.Logger, + "stuartferguson/estatemanagement", new List + { + testNetwork, + Setup.DatabaseServerNetwork + }, traceFolder, null, + this.SecurityServiceContainerName, + this.EventStoreContainerName, + Setup.SqlServerContainerName, + "sa", + "thisisalongpassword123!", + ("serviceClient", "Secret1")); + + IContainerService securityServiceContainer = DockerHelper.SetupSecurityServiceContainer(this.SecurityServiceContainerName, this.Logger, "stuartferguson/securityservice", testNetwork, @@ -145,7 +166,7 @@ public override async Task StartContainersForScenarioRun(String scenarioName) dockerCredentials, true); - IContainerService transactionProcessorContainer = DockerHelper.SetupTransactionProcessorContainer(transactionProcessorContainerName, + IContainerService transactionProcessorContainer = DockerHelper.SetupTransactionProcessorContainer(this.TransactionProcessorContainerName, this.Logger, "transactionprocessor", new List @@ -154,17 +175,35 @@ public override async Task StartContainersForScenarioRun(String scenarioName) }, traceFolder, dockerCredentials, - securityServiceContainerName, - estateManagementApiContainerName, - eventStoreContainerName, + this.SecurityServiceContainerName, + this.EstateManagementContainerName, + this.EventStoreContainerName, ("serviceClient", "Secret1")); + IContainerService estateReportingContainer = DockerHelper.SetupEstateReportingContainer(this.EstateReportingContainerName, + this.Logger, + "stuartferguson/estatereporting", + new List + { + testNetwork, + Setup.DatabaseServerNetwork + }, + traceFolder, + dockerCredentials, + this.SecurityServiceContainerName, + Setup.SqlServerContainerName, + "sa", + "thisisalongpassword123!", + ("serviceClient", "Secret1"), + true); + this.Containers.AddRange(new List { eventStoreContainer, estateManagementContainer, securityServiceContainer, - transactionProcessorContainer + transactionProcessorContainer, + estateReportingContainer }); // Cache the ports @@ -182,6 +221,122 @@ public override async Task StartContainersForScenarioRun(String scenarioName) this.EstateClient = new EstateClient(EstateManagementBaseAddressResolver, httpClient); this.SecurityServiceClient = new SecurityServiceClient(SecurityServiceBaseAddressResolver, httpClient); this.TransactionProcessorClient = new TransactionProcessorClient(TransactionProcessorBaseAddressResolver, httpClient); + + await PopulateSubscriptionServiceConfiguration().ConfigureAwait(false); + + IContainerService subscriptionServiceContainer = DockerHelper.SetupSubscriptionServiceContainer(this.SubscriptionServiceContainerName, + this.Logger, + "stuartferguson/subscriptionservicehost", + new List + { + testNetwork, + Setup.DatabaseServerNetwork + }, + traceFolder, + dockerCredentials, + this.SecurityServiceContainerName, + Setup.SqlServerContainerName, + "sa", + "thisisalongpassword123!", + this.TestId, + ("serviceClient", "Secret1"), + true); + + this.Containers.Add(subscriptionServiceContainer); + } + + protected async Task PopulateSubscriptionServiceConfiguration() + { + String connectionString = Setup.GetLocalConnectionString("SubscriptionServiceConfiguration"); + + await using (SqlConnection connection = new SqlConnection(connectionString)) + { + try + { + await connection.OpenAsync(CancellationToken.None).ConfigureAwait(false); + + // Create an Event Store Server + await this.InsertEventStoreServer(connection, this.EventStoreContainerName).ConfigureAwait(false); + + String endPointUri = $"http://{this.EstateReportingContainerName}:5005/api/domainevents"; + // Add Route for Estate Aggregate Events + await this.InsertSubscription(connection, "$ce-EstateAggregate", "Reporting", endPointUri).ConfigureAwait(false); + + // Add Route for Merchant Aggregate Events + await this.InsertSubscription(connection, "$ce-MerchantAggregate", "Reporting", endPointUri).ConfigureAwait(false); + + await connection.CloseAsync().ConfigureAwait(false); + } + catch (Exception e) + { + throw; + } + } + } + protected async Task CleanUpSubscriptionServiceConfiguration() + { + String connectionString = Setup.GetLocalConnectionString("SubscriptionServiceConfiguration"); + + await using (SqlConnection connection = new SqlConnection(connectionString)) + { + await connection.OpenAsync(CancellationToken.None).ConfigureAwait(false); + + // Delete the Event Store Server + await this.DeleteEventStoreServer(connection).ConfigureAwait(false); + + // Delete the Subscriptions + await this.DeleteSubscriptions(connection).ConfigureAwait(false); + + await connection.CloseAsync().ConfigureAwait(false); + } + } + + protected async Task InsertEventStoreServer(SqlConnection openConnection, String eventStoreContainerName) + { + String esConnectionString = $"ConnectTo=tcp://admin:changeit@{eventStoreContainerName}:{DockerHelper.EventStoreTcpDockerPort};VerboseLogging=true;"; + SqlCommand command = openConnection.CreateCommand(); + command.CommandText = $"INSERT INTO EventStoreServer(EventStoreServerId, ConnectionString,Name) SELECT '{this.TestId}', '{esConnectionString}', 'TestEventStore'"; + command.CommandType = CommandType.Text; + await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); + } + + protected async Task DeleteEventStoreServer(SqlConnection openConnection) + { + SqlCommand command = openConnection.CreateCommand(); + command.CommandText = $"DELETE FROM EventStoreServer WHERE EventStoreServerId = '{this.TestId}'"; + command.CommandType = CommandType.Text; + await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); + } + + protected async Task DeleteSubscriptions(SqlConnection openConnection) + { + SqlCommand command = openConnection.CreateCommand(); + command.CommandText = $"DELETE FROM Subscription WHERE EventStoreId = '{this.TestId}'"; + command.CommandType = CommandType.Text; + await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); + } + + protected async Task InsertSubscription(SqlConnection openConnection, String streamName, String groupName, String endPointUri) + { + SqlCommand command = openConnection.CreateCommand(); + command.CommandText = $"INSERT INTO subscription(SubscriptionId, EventStoreId, StreamName, GroupName, EndPointUri, StreamPosition) SELECT '{Guid.NewGuid()}', '{this.TestId}', '{streamName}', '{groupName}', '{endPointUri}', null"; + command.CommandType = CommandType.Text; + await command.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); + } + + private async Task RemoveEstateReadModel() + { + List estateIdList = this.TestingContext.GetAllEstateIds(); + + foreach (Guid estateId in estateIdList) + { + String databaseName = $"EstateReportingReadModel{estateId}"; + + // Build the connection string (to master) + String connectionString = Setup.GetLocalConnectionString(databaseName); + EstateReportingContext context = new EstateReportingContext(connectionString); + await context.Database.EnsureDeletedAsync(CancellationToken.None); + } } /// @@ -189,6 +344,10 @@ public override async Task StartContainersForScenarioRun(String scenarioName) /// public override async Task StopContainersForScenarioRun() { + await CleanUpSubscriptionServiceConfiguration().ConfigureAwait(false); + + await RemoveEstateReadModel().ConfigureAwait(false); + if (this.Containers.Any()) { foreach (IContainerService containerService in this.Containers) diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs index 35c6f4e8..d24e674e 100644 --- a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs +++ b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs @@ -39,7 +39,7 @@ public async Task StartSystem() logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName); LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); - this.TestingContext.DockerHelper = new DockerHelper(logger); + 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); diff --git a/TransactionProcessor.IntegrationTests/Common/Setup.cs b/TransactionProcessor.IntegrationTests/Common/Setup.cs index 18299510..cf609d0e 100644 --- a/TransactionProcessor.IntegrationTests/Common/Setup.cs +++ b/TransactionProcessor.IntegrationTests/Common/Setup.cs @@ -12,7 +12,9 @@ namespace TransactionProcessor.IntegrationTests.Common using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; + using global::Shared.Logger; using Microsoft.Data.SqlClient; + using NLog; using Shouldly; using TechTalk.SpecFlow; @@ -23,26 +25,47 @@ public class Setup private static String DbConnectionStringWithNoDatabase; public static INetworkService DatabaseServerNetwork; + public static String SqlServerContainerName = "shareddatabasesqlserver"; + + public const String SqlUserName = "sa"; + + public const String SqlPassword = "thisisalongpassword123!"; [BeforeTestRun] protected static void GlobalSetup() { + ShouldlyConfiguration.DefaultTaskTimeout = TimeSpan.FromMinutes(1); + (String, String, String) dockerCredentials = ("https://www.docker.com", "stuartferguson", "Sc0tland"); // Setup a network for the DB Server DatabaseServerNetwork = global::Shared.IntegrationTesting.DockerHelper.SetupTestNetwork("sharednetwork", true); + NlogLogger logger = new NlogLogger(); + logger.Initialise(LogManager.GetLogger("Specflow"), "Specflow"); + LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); + // Start the Database Server here - DbConnectionStringWithNoDatabase = global::Shared.IntegrationTesting.DockerHelper.StartSqlContainerWithOpenConnection("shareddatabasesqlserver", - null, - "stuartferguson/subscriptionservicedatabasesqlserver", - Setup.DatabaseServerNetwork, - "", - dockerCredentials); + DatabaseServerContainer = global::Shared.IntegrationTesting.DockerHelper.StartSqlContainerWithOpenConnection(Setup.SqlServerContainerName, + logger, + "stuartferguson/subscriptionservicedatabasesqlserver", + Setup.DatabaseServerNetwork, + "", + dockerCredentials, + Setup.SqlUserName, + Setup.SqlPassword); } public static String GetConnectionString(String databaseName) { - return $"{DbConnectionStringWithNoDatabase} database={databaseName};"; + return $"server={Setup.DatabaseServerContainer.Name};database={databaseName};user id={Setup.SqlUserName};password={Setup.SqlPassword}"; } + + public static String GetLocalConnectionString(String databaseName) + { + Int32 databaseHostPort = Setup.DatabaseServerContainer.ToHostExposedEndpoint("1433/tcp").Port; + + return $"server=localhost,{databaseHostPort};database={databaseName};user id={Setup.SqlUserName};password={Setup.SqlPassword}"; + } + } } diff --git a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs index 9686e420..29fb06b7 100644 --- a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs +++ b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs @@ -72,6 +72,10 @@ public EstateDetails GetEstateDetails(TableRow tableRow) return estateDetails; } + public List GetAllEstateIds() + { + return this.Estates.Select(e => e.EstateId).ToList(); + } public EstateDetails GetEstateDetails(String estateName) { EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateName == estateName); diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs index 946c6361..b79468e1 100644 --- a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -67,7 +67,13 @@ public async Task WhenICreateTheFollowingEstates(Table table) { EstateDetails estateDetails = this.TestingContext.GetEstateDetails(tableRow); - EstateResponse estate = await this.TestingContext.DockerHelper.EstateClient.GetEstate(this.TestingContext.AccessToken, estateDetails.EstateId, CancellationToken.None).ConfigureAwait(false); + EstateResponse estate = null; + await Retry.For(async () => + { + estate = await this.TestingContext.DockerHelper.EstateClient + .GetEstate(this.TestingContext.AccessToken, estateDetails.EstateId, CancellationToken.None).ConfigureAwait(false); + estate.ShouldNotBeNull(); + }).ConfigureAwait(false); estate.EstateName.ShouldBe(estateDetails.EstateName); } diff --git a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj index be1733a7..9d12ed35 100644 --- a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj +++ b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj @@ -7,14 +7,15 @@ - + - + + - - - + + + diff --git a/TransactionProcessor.Transaction.DomainEvents/TransactionProcessor.Transaction.DomainEvents.csproj b/TransactionProcessor.Transaction.DomainEvents/TransactionProcessor.Transaction.DomainEvents.csproj index a35ece09..a135d524 100644 --- a/TransactionProcessor.Transaction.DomainEvents/TransactionProcessor.Transaction.DomainEvents.csproj +++ b/TransactionProcessor.Transaction.DomainEvents/TransactionProcessor.Transaction.DomainEvents.csproj @@ -5,7 +5,7 @@ - + diff --git a/TransactionProcessor.TransactionAgrgegate/TransactionProcessor.TransactionAggregate.csproj b/TransactionProcessor.TransactionAgrgegate/TransactionProcessor.TransactionAggregate.csproj index 905a57a9..a9f3842c 100644 --- a/TransactionProcessor.TransactionAgrgegate/TransactionProcessor.TransactionAggregate.csproj +++ b/TransactionProcessor.TransactionAgrgegate/TransactionProcessor.TransactionAggregate.csproj @@ -5,7 +5,7 @@ - + diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs index b0b25024..abfcc67c 100644 --- a/TransactionProcessor/Startup.cs +++ b/TransactionProcessor/Startup.cs @@ -28,6 +28,7 @@ namespace TransactionProcessor using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Extensions.Options; + using Microsoft.IdentityModel.Logging; using Models; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -170,7 +171,9 @@ private void ConfigureMiddlewareServices(IServiceCollection services) }); services.AddSwaggerExamplesFromAssemblyOf(); - + + IdentityModelEventSource.ShowPII = true; + services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; diff --git a/TransactionProcessor/TransactionProcessor.csproj b/TransactionProcessor/TransactionProcessor.csproj index 2db70b9b..f92cedb5 100644 --- a/TransactionProcessor/TransactionProcessor.csproj +++ b/TransactionProcessor/TransactionProcessor.csproj @@ -20,7 +20,7 @@ - +