diff --git a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj index 66ef0e48..71b01d13 100644 --- a/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj +++ b/TransactionProcessor.BusinessLogic/TransactionProcessor.BusinessLogic.csproj @@ -5,9 +5,9 @@ - - - + + + diff --git a/TransactionProcessor.Client/ITransactionProcessorClient.cs b/TransactionProcessor.Client/ITransactionProcessorClient.cs index 38c6bd9a..bd841d71 100644 --- a/TransactionProcessor.Client/ITransactionProcessorClient.cs +++ b/TransactionProcessor.Client/ITransactionProcessorClient.cs @@ -1,5 +1,6 @@ namespace TransactionProcessor.Client { + using System; using System.Threading; using System.Threading.Tasks; using DataTransferObjects; @@ -14,7 +15,8 @@ public interface ITransactionProcessorClient /// The transaction request. /// The cancellation token. /// - Task PerformTransaction(SerialisedMessage transactionRequest, + Task PerformTransaction(String accessToken, + SerialisedMessage transactionRequest, CancellationToken cancellationToken); #endregion diff --git a/TransactionProcessor.Client/TransactionProcessorClient.cs b/TransactionProcessor.Client/TransactionProcessorClient.cs index 5b547c70..5c707185 100644 --- a/TransactionProcessor.Client/TransactionProcessorClient.cs +++ b/TransactionProcessor.Client/TransactionProcessorClient.cs @@ -2,6 +2,7 @@ { using System; using System.Net.Http; + using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,7 @@ /// /// /// - /// + /// /// public class TransactionProcessorClient : ClientProxyBase, ITransactionProcessorClient { @@ -48,10 +49,12 @@ public TransactionProcessorClient(Func baseAddressResolver, /// /// Performs the transaction. /// + /// The access token. /// The transaction request. /// The cancellation token. /// - public async Task PerformTransaction(SerialisedMessage transactionRequest, + public async Task PerformTransaction(String accessToken, + SerialisedMessage transactionRequest, CancellationToken cancellationToken) { SerialisedMessage response = null; @@ -65,7 +68,7 @@ public async Task PerformTransaction(SerialisedMessage transa StringContent httpContent = new StringContent(requestSerialised, Encoding.UTF8, "application/json"); // Add the access token to the client headers - //this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); // Make the Http Call here HttpResponseMessage httpResponse = await this.HttpClient.PostAsync(requestUri, httpContent, cancellationToken); diff --git a/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs b/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs index d8e58f81..b26a5c66 100644 --- a/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs +++ b/TransactionProcessor.DataTransferObjects/LogonTransactionRequest.cs @@ -19,22 +19,6 @@ public class LogonTransactionRequest : DataTransferObject /// public String IMEINumber { get; set; } - /// - /// Gets or sets the merchant identifier. - /// - /// - /// The merchant identifier. - /// - public Guid MerchantId { get; set; } - - /// - /// Gets or sets the estate identifier. - /// - /// - /// The estate identifier. - /// - public Guid EstateId { get; set; } - /// /// Gets or sets the transaction date time. /// diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs index 38eb014f..b037b0b2 100644 --- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Net.Http; + using System.Threading; using System.Threading.Tasks; using Client; using Ductus.FluentDocker.Builders; @@ -12,29 +13,41 @@ using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; using EstateManagement.Client; + using global::Shared.Logger; + using SecurityService.Client; public class DockerHelper { + private readonly NlogLogger Logger; + protected INetworkService TestNetwork; - + + public Int32 SecurityServicePort; protected Int32 EstateManagementPort; protected Int32 TransactionProcessorPort; protected Int32 EventStorePort; + public IContainerService SecurityServiceContainer; public IContainerService EstateManagementContainer; public IContainerService TransactionProcessorContainer; protected IContainerService EventStoreContainer; public IEstateClient EstateClient; public ITransactionProcessorClient TransactionProcessorClient; - //public HttpClient HttpClient; + public ISecurityServiceClient SecurityServiceClient; protected String EventStoreConnectionString; + public String SecurityServiceContainerName; protected String EstateManagementContainerName; protected String TransactionProcessorContainerName; protected String EventStoreContainerName; + public DockerHelper(NlogLogger logger) + { + this.Logger = logger; + } + private void SetupTestNetwork() { // Build a network @@ -67,6 +80,7 @@ public async Task StartContainersForScenarioRun(String scenarioName) this.TestId = testGuid; // Setup the container names + this.SecurityServiceContainerName = $"securityservice{testGuid:N}"; this.EstateManagementContainerName = $"estate{testGuid:N}"; this.TransactionProcessorContainerName = $"txnprocessor{testGuid:N}"; this.EventStoreContainerName = $"eventstore{testGuid:N}"; @@ -75,6 +89,7 @@ public async Task StartContainersForScenarioRun(String scenarioName) $"EventStoreSettings:ConnectionString=ConnectTo=tcp://admin:changeit@{this.EventStoreContainerName}:1113;VerboseLogging=true;"; this.SetupTestNetwork(); + this.SetupSecurityServiceContainer(traceFolder); this.SetupEventStoreContainer(traceFolder); this.SetupEstateManagementContainer(traceFolder); this.SetupTransactionProcessorContainer(traceFolder); @@ -83,14 +98,17 @@ public async Task StartContainersForScenarioRun(String scenarioName) this.EstateManagementPort = this.EstateManagementContainer.ToHostExposedEndpoint("5000/tcp").Port; this.TransactionProcessorPort = this.TransactionProcessorContainer.ToHostExposedEndpoint("5002/tcp").Port; this.EventStorePort = this.EventStoreContainer.ToHostExposedEndpoint("2113/tcp").Port; + this.SecurityServicePort = this.SecurityServiceContainer.ToHostExposedEndpoint("5001/tcp").Port; // Setup the base address resolver Func estateManagementBaseAddressResolver = api => $"http://127.0.0.1:{this.EstateManagementPort}"; Func transactionProcessorBaseAddressResolver = api => $"http://127.0.0.1:{this.TransactionProcessorPort}"; + Func securityServiceBaseAddressResolver = api => $"http://127.0.0.1:{this.SecurityServicePort}"; - this.EstateClient = new EstateClient(estateManagementBaseAddressResolver, new HttpClient()); - this.TransactionProcessorClient = new TransactionProcessorClient(transactionProcessorBaseAddressResolver, new HttpClient()); - + HttpClient httpClient = new HttpClient(); + this.EstateClient = new EstateClient(estateManagementBaseAddressResolver, httpClient); + this.TransactionProcessorClient = new TransactionProcessorClient(transactionProcessorBaseAddressResolver, httpClient); + this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver, httpClient); // TODO: Use this to talk to txn processor until we have a client //this.HttpClient = new HttpClient(); //this.HttpClient.BaseAddress = new Uri(transactionProcessorBaseAddressResolver(String.Empty)); @@ -100,6 +118,13 @@ public async Task StopContainersForScenarioRun() { try { + if (this.SecurityServiceContainer != null) + { + this.SecurityServiceContainer.StopOnDispose = true; + this.SecurityServiceContainer.RemoveOnDispose = true; + this.SecurityServiceContainer.Dispose(); + } + if (this.TransactionProcessorContainer != null) { this.TransactionProcessorContainer.StopOnDispose = true; @@ -133,6 +158,27 @@ public async Task StopContainersForScenarioRun() } } + private void SetupSecurityServiceContainer(String traceFolder) + { + this.Logger.LogInformation("About to Start Security Container"); + + this.SecurityServiceContainer = new Builder().UseContainer().WithName(this.SecurityServiceContainerName) + .WithEnvironment($"ServiceOptions:PublicOrigin=http://{this.SecurityServiceContainerName}:5001", + $"ServiceOptions:IssuerUrl=http://{this.SecurityServiceContainerName}:5001", + "ASPNETCORE_ENVIRONMENT=IntegrationTest", + "urls=http://*:5001") + .WithCredential("https://www.docker.com", "stuartferguson", "Sc0tland") + .UseImage("stuartferguson/securityservice").ExposePort(5001).UseNetwork(new List + { + this.TestNetwork + }.ToArray()) + .Mount(traceFolder, "/home/txnproc/trace", MountType.ReadWrite).Build().Start().WaitForPort("5001/tcp", 30000); + Thread.Sleep(20000); + + this.Logger.LogInformation("Security Service Container Started"); + + } + private void SetupEstateManagementContainer(String traceFolder) { // Management API Container @@ -140,6 +186,8 @@ private void SetupEstateManagementContainer(String traceFolder) .UseContainer() .WithName(this.EstateManagementContainerName) .WithEnvironment(this.EventStoreConnectionString, + $"AppSettings:SecurityService=http://{this.SecurityServiceContainerName}:5001", + $"SecurityConfiguration:Authority=http://{this.SecurityServiceContainerName}:5001", "urls=http://*:5000") //, //"AppSettings:MigrateDatabase=true", //"EventStoreSettings:START_PROJECTIONS=true", @@ -159,7 +207,9 @@ private void SetupTransactionProcessorContainer(String traceFolder) this.TransactionProcessorContainer = new Builder() .UseContainer() .WithName(this.TransactionProcessorContainerName) - .WithEnvironment(this.EventStoreConnectionString) //, + .WithEnvironment(this.EventStoreConnectionString, + $"AppSettings:SecurityService=http://{this.SecurityServiceContainerName}:5001", + $"SecurityConfiguration:Authority=http://{this.SecurityServiceContainerName}:5001") //, //"AppSettings:MigrateDatabase=true", //"EventStoreSettings:START_PROJECTIONS=true", //"EventStoreSettings:ContinuousProjectionsFolder=/app/projections/continuous") diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs index ff208e86..af7fc5de 100644 --- a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs +++ b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs @@ -10,6 +10,8 @@ namespace TransactionProcessor.IntegrationTests.Common using Ductus.FluentDocker.Extensions; using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; + using global::Shared.Logger; + using NLog; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Plugins; @@ -31,9 +33,18 @@ public GenericSteps(ScenarioContext scenarioContext, [BeforeScenario] public async Task StartSystem() { + // Initialise a logger String scenarioName = this.ScenarioContext.ScenarioInfo.Title.Replace(" ", ""); - this.TestingContext.DockerHelper = new DockerHelper(); + NlogLogger logger = new NlogLogger(); + logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName); + LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); + + this.TestingContext.DockerHelper = new DockerHelper(logger); + 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"); + Thread.Sleep(20000); } diff --git a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs index f730e766..4faefd28 100644 --- a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs +++ b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs @@ -4,28 +4,252 @@ namespace TransactionProcessor.IntegrationTests.Common { + using System.Linq; using DataTransferObjects; + using global::Shared.Logger; + using Shouldly; + using TechTalk.SpecFlow; + + //public class TestingContext + //{ + // public TestingContext() + // { + // this.Estates = new Dictionary(); + // this.Merchants = new Dictionary(); + // this.Operators = new Dictionary(); + // this.EstateMerchants = new Dictionary>(); + // this.TransactionResponses=new Dictionary(); + // } + + // public DockerHelper DockerHelper { get; set; } + + // public Dictionary Estates { get; set; } + // public Dictionary Merchants { get; set; } + + // public Dictionary Operators { get; set; } + + // public Dictionary> EstateMerchants { get; set; } + + // public Dictionary TransactionResponses { get; set; } + + + //} public class TestingContext { public TestingContext() { - this.Estates = new Dictionary(); + this.Estates = new List(); + this.Clients = new List(); + } + + public NlogLogger Logger { get; set; } + + public DockerHelper DockerHelper { get; set; } + + private List Clients; + + private List Estates; + + public String AccessToken { get; set; } + + public EstateDetails GetEstateDetails(TableRow tableRow) + { + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + + EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateName == estateName); + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + public EstateDetails GetEstateDetails(String estateName) + { + EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateName == estateName); + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + public EstateDetails GetEstateDetails(Guid estateId) + { + EstateDetails estateDetails = this.Estates.SingleOrDefault(e => e.EstateId== estateId); + + estateDetails.ShouldNotBeNull(); + + return estateDetails; + } + + public void AddEstateDetails(Guid estateId, String estateName) + { + this.Estates.Add(EstateDetails.Create(estateId, estateName)); + } + + 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 class EstateDetails + { + private EstateDetails(Guid estateId, String estateName) + { + this.EstateId = estateId; + this.EstateName = estateName; this.Merchants = new Dictionary(); this.Operators = new Dictionary(); - this.EstateMerchants = new Dictionary>(); - this.TransactionResponses=new Dictionary(); + this.MerchantUsers = new Dictionary>(); + this.TransactionResponses = new Dictionary<(Guid merchantId, String transactionNumber), SerialisedMessage>(); } - public DockerHelper DockerHelper { get; set; } + public void AddTransactionResponse(Guid merchantId, + String transactionNumber, + SerialisedMessage transactionResponse) + { + this.TransactionResponses.Add((merchantId, transactionNumber), transactionResponse); + } + + public SerialisedMessage GetTransactionResponse(Guid merchantId, + String transactionNumber) + { + KeyValuePair<(Guid merchantId, String transactionNumber), SerialisedMessage> transactionResponse = + this.TransactionResponses.Where(t => t.Key.merchantId == merchantId && t.Key.transactionNumber == transactionNumber).SingleOrDefault(); + + return transactionResponse.Value; + } - public Dictionary Estates { get; set; } - public Dictionary Merchants { get; set; } + private Dictionary<(Guid merchantId, String transactionNumber), SerialisedMessage> TransactionResponses { get; set; } - public Dictionary Operators { get; set; } + public String EstateUser { get; private set; } + public String EstatePassword { get; private set; } - public Dictionary> EstateMerchants { get; set; } + public String AccessToken { get; private set; } + + public static EstateDetails Create(Guid estateId, + String estateName) + { + return new EstateDetails(estateId, estateName); + } + + public void AddOperator(Guid operatorId, + String operatorName) + { + this.Operators.Add(operatorName, operatorId); + } - public Dictionary TransactionResponses { get; set; } + public void AddMerchant(Guid merchantId, + String merchantName) + { + this.Merchants.Add(merchantName, merchantId); + } + + public Guid GetMerchantId(String merchantName) + { + return this.Merchants.Single(m => m.Key == merchantName).Value; + } + + public Guid GetOperatorId(String operatorName) + { + return this.Operators.Single(o => o.Key == operatorName).Value; + } + + public void SetEstateUser(String userName, + String password) + { + this.EstateUser = userName; + this.EstatePassword = password; + } + + public void AddMerchantUser(String merchantName, + String userName, + String password) + { + if (this.MerchantUsers.ContainsKey(merchantName)) + { + Dictionary merchantUsersList = this.MerchantUsers[merchantName]; + if (merchantUsersList.ContainsKey(userName) == false) + { + merchantUsersList.Add(userName, password); + } + } + else + { + Dictionary merchantUsersList = new Dictionary(); + merchantUsersList.Add(userName, password); + this.MerchantUsers.Add(merchantName, merchantUsersList); + } + } + + public void AddMerchantUserToken(String merchantName, + String userName, + String token) + { + if (this.MerchantUsersTokens.ContainsKey(merchantName)) + { + Dictionary merchantUsersList = this.MerchantUsersTokens[merchantName]; + if (merchantUsersList.ContainsKey(userName) == false) + { + merchantUsersList.Add(userName, token); + } + } + else + { + Dictionary merchantUsersList = new Dictionary(); + merchantUsersList.Add(userName, token); + this.MerchantUsersTokens.Add(merchantName, merchantUsersList); + } + } + + public void SetEstateUserToken(String accessToken) + { + this.AccessToken = accessToken; + } + + public Guid EstateId { get; private set; } + public String EstateName { get; private set; } + + private Dictionary Operators; + + private Dictionary Merchants; + + private Dictionary> MerchantUsers; + private Dictionary> MerchantUsersTokens; + } + + 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/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature index 7e89f95f..b245bc76 100644 --- a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature +++ b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature @@ -2,27 +2,49 @@ Feature: LogonTransaction Background: + + Given the following api resources exist + | ResourceName | DisplayName | Secret | Scopes | UserClaims | + | estateManagement | Estate Managememt REST | Secret1 | estateManagement | MerchantId, EstateId, role | + | transactionProcessor | Transaction Processor REST | Secret1 | transactionProcessor | | + + Given the following clients exist + | ClientId | ClientName | Secret | AllowedScopes | AllowedGrantTypes | + | serviceClient | Service Client | Secret1 | estateManagement,transactionProcessor | client_credentials | + + Given I have a token to access the estate management and transaction processor resources + | ClientId | + | serviceClient | + Given I have created the following estates | EstateName | | Test Estate 1 | | Test Estate 2 | + Given I have created the following operators + | EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber | + | Test Estate 1 | Test Operator 1 | True | True | + Given I create the following merchants | MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName | | Test Merchant 1 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | | Test Merchant 2 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 2 | testcontact2@merchant2.co.uk | Test Estate 1 | | Test Merchant 3 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 3 | testcontact3@merchant2.co.uk | Test Estate 2 | + Given I have assigned the following operator to the merchants + | OperatorName | MerchantName | MerchantNumber | TerminalNumber | EstateName | + | Test Operator 1 | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + @PRTest Scenario: Logon Transaction When I perform the following transactions - | DateTime | TransactionNumber | TransactionType | MerchantName | IMEINumber | - | Today | 1 | Logon | Test Merchant 1 | 123456789 | - | Today | 2 | Logon | Test Merchant 2 | 123456789 | - | Today | 3 | Logon | Test Merchant 3 | 123456789 | + | DateTime | TransactionNumber | TransactionType | MerchantName | IMEINumber | EstateName | + | Today | 1 | Logon | Test Merchant 1 | 123456789 | Test Estate 1 | + | Today | 2 | Logon | Test Merchant 2 | 123456789 | Test Estate 1 | + | Today | 3 | Logon | Test Merchant 3 | 123456789 | Test Estate 2 | Then transaction response should contain the following information - | TransactionNumber | ResponseCode | ResponseMessage | - | 1 | 0000 | SUCCESS | - | 2 | 0000 | SUCCESS | - | 3 | 0000 | SUCCESS | + | EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage | + | Test Estate 1 | Test Merchant 1 | 1 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 2 | 2 | 0000 | SUCCESS | + | Test Estate 2 | Test Merchant 3 | 3 | 0000 | SUCCESS | diff --git a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs index b524a484..b98b05f0 100644 --- a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs +++ b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs @@ -21,7 +21,7 @@ namespace TransactionProcessor.IntegrationTests.LogonTransaction [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [Xunit.TraitAttribute("Category", "base")] [Xunit.TraitAttribute("Category", "shared")] - public partial class LogonTransactionFeature : Xunit.IClassFixture, System.IDisposable + public partial class LogonTransactionFeature : object, Xunit.IClassFixture, System.IDisposable { private static TechTalk.SpecFlow.ITestRunner testRunner; @@ -35,7 +35,7 @@ public partial class LogonTransactionFeature : Xunit.IClassFixture