From d3d5d57311f5edc84b43cd844506f1c1b448fdd9 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 6 Dec 2019 15:36:24 +0000 Subject: [PATCH 1/3] Skelton Service Created --- .dockerignore | 25 ++ .gitignore | 1 + .../Common/DockerHelper.cs | 199 ++++++++++++ .../Common/GenericSteps.cs | 39 +++ .../Common/Retry.cs | 72 +++++ .../Common/Setup.cs | 117 ++++++++ .../Common/SpecflowTableHelper.cs | 88 ++++++ .../Common/TestingContext.cs | 31 ++ .../LogonTransaction/LogonTransaction.feature | 29 ++ .../LogonTransaction.feature.cs | 225 ++++++++++++++ .../Shared/SharedSteps.cs | 283 ++++++++++++++++++ ...ansactionProcessor.IntegrationTests.csproj | 35 +++ .../ProcessLogonTransactionRequestHandler.cs | 20 ++ .../ProcessLogonTransactionRequest.cs | 122 ++++++++ ...ansactionProcessorACL.BusinessLogic.csproj | 15 + .../LogonTransactionRequestMessage.cs | 9 + .../SaleTransactionRequestMessage.cs | 9 + ...ionProcessorACL.DataTransferObjects.csproj | 11 + .../TransactionRequestMessage.cs | 11 + .../ProcessLogonTransactionResponse.cs | 23 ++ .../TransactionProcessorACL.Models.csproj | 7 + .../General/BootstrapperTests.cs | 131 ++++++++ .../TransactionProcessorACL.Tests.csproj | 29 ++ TransactionProcessorACL.sln | 67 +++++ .../Common/ConfigureSwaggerOptions.cs | 86 ++++++ .../Common/SwaggerDefaultValues.cs | 52 ++++ .../Common/SwaggerJsonConverter.cs | 76 +++++ .../Controllers/TransactionController.cs | 71 +++++ TransactionProcessorACL/Dockerfile | 23 ++ TransactionProcessorACL/NuGet.Config | 18 ++ TransactionProcessorACL/Program.cs | 42 +++ TransactionProcessorACL/Startup.cs | 170 +++++++++++ .../TransactionProcessorACL.csproj | 32 ++ TransactionProcessorACL/appsettings.json | 10 + TransactionProcessorACL/hosting.json | 3 + TransactionProcessorACL/nlog.config | 19 ++ .../nlog.development.config | 19 ++ 37 files changed, 2219 insertions(+) create mode 100644 .dockerignore create mode 100644 TransactionProcessor.IntegrationTests/Common/DockerHelper.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/GenericSteps.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/Retry.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/Setup.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs create mode 100644 TransactionProcessor.IntegrationTests/Common/TestingContext.cs create mode 100644 TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature create mode 100644 TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs create mode 100644 TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs create mode 100644 TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj create mode 100644 TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs create mode 100644 TransactionProcessorACL.BusinessLogic/Requests/ProcessLogonTransactionRequest.cs create mode 100644 TransactionProcessorACL.BusinessLogic/TransactionProcessorACL.BusinessLogic.csproj create mode 100644 TransactionProcessorACL.DataTransferObjects/LogonTransactionRequestMessage.cs create mode 100644 TransactionProcessorACL.DataTransferObjects/SaleTransactionRequestMessage.cs create mode 100644 TransactionProcessorACL.DataTransferObjects/TransactionProcessorACL.DataTransferObjects.csproj create mode 100644 TransactionProcessorACL.DataTransferObjects/TransactionRequestMessage.cs create mode 100644 TransactionProcessorACL.Models/ProcessLogonTransactionResponse.cs create mode 100644 TransactionProcessorACL.Models/TransactionProcessorACL.Models.csproj create mode 100644 TransactionProcessorACL.Tests/General/BootstrapperTests.cs create mode 100644 TransactionProcessorACL.Tests/TransactionProcessorACL.Tests.csproj create mode 100644 TransactionProcessorACL.sln create mode 100644 TransactionProcessorACL/Common/ConfigureSwaggerOptions.cs create mode 100644 TransactionProcessorACL/Common/SwaggerDefaultValues.cs create mode 100644 TransactionProcessorACL/Common/SwaggerJsonConverter.cs create mode 100644 TransactionProcessorACL/Controllers/TransactionController.cs create mode 100644 TransactionProcessorACL/Dockerfile create mode 100644 TransactionProcessorACL/NuGet.Config create mode 100644 TransactionProcessorACL/Program.cs create mode 100644 TransactionProcessorACL/Startup.cs create mode 100644 TransactionProcessorACL/TransactionProcessorACL.csproj create mode 100644 TransactionProcessorACL/appsettings.json create mode 100644 TransactionProcessorACL/hosting.json create mode 100644 TransactionProcessorACL/nlog.config create mode 100644 TransactionProcessorACL/nlog.development.config diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3e759b7..5ae0db9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.user *.userosscache *.sln.docstates +*.[Dd]evelopment.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs new file mode 100644 index 0000000..32cd19e --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using System.Net.Http; + using System.Threading.Tasks; + using Client; + using Ductus.FluentDocker.Builders; + using Ductus.FluentDocker.Model.Builders; + using Ductus.FluentDocker.Services; + using Ductus.FluentDocker.Services.Extensions; + using EstateManagement.Client; + + public class DockerHelper + { + protected INetworkService TestNetwork; + + protected Int32 EstateManagementPort; + protected Int32 TransactionProcessorPort; + protected Int32 TransactionProcessorACLPort; + protected Int32 EventStorePort; + + protected IContainerService EstateManagementContainer; + protected IContainerService TransactionProcessorContainer; + protected IContainerService TransactionProcessorACLContainer; + protected IContainerService EventStoreContainer; + + public IEstateClient EstateClient; + public HttpClient HttpClient; + + protected String EventStoreConnectionString; + + protected String EstateManagementContainerName; + protected String TransactionProcessorContainerName; + protected String TransactionProcessorACLContainerName; + protected String EventStoreContainerName; + + private void SetupTestNetwork() + { + // Build a network + this.TestNetwork = new Ductus.FluentDocker.Builders.Builder().UseNetwork($"testnetwork{Guid.NewGuid()}").Build(); + } + public Guid TestId; + private void SetupEventStoreContainer(String traceFolder) + { + // Event Store Container + this.EventStoreContainer = new Ductus.FluentDocker.Builders.Builder() + .UseContainer() + .UseImage("eventstore/eventstore:release-5.0.2") + .ExposePort(2113) + .ExposePort(1113) + .WithName(this.EventStoreContainerName) + .WithEnvironment("EVENTSTORE_RUN_PROJECTIONS=all", "EVENTSTORE_START_STANDARD_PROJECTIONS=true") + .UseNetwork(this.TestNetwork) + .Mount(traceFolder, "/var/log/eventstore", MountType.ReadWrite) + .Build() + .Start().WaitForPort("2113/tcp", 30000); + } + + public async Task StartContainersForScenarioRun(String scenarioName) + { + String traceFolder = $"/home/ubuntu/estatemanagement/trace/{scenarioName}/"; + + Logging.Enabled(); + + Guid testGuid = Guid.NewGuid(); + this.TestId = testGuid; + + // Setup the container names + this.EstateManagementContainerName = $"estate{testGuid:N}"; + this.TransactionProcessorContainerName = $"txnprocessor{testGuid:N}"; + this.TransactionProcessorACLContainerName = $"txnprocessoracl{testGuid:N}"; + this.EventStoreContainerName = $"eventstore{testGuid:N}"; + + this.EventStoreConnectionString = + $"EventStoreSettings:ConnectionString=ConnectTo=tcp://admin:changeit@{this.EventStoreContainerName}:1113;VerboseLogging=true;"; + + this.SetupTestNetwork(); + this.SetupEventStoreContainer(traceFolder); + this.SetupEstateManagementContainer(traceFolder); + this.SetupTransactionProcessorContainer(traceFolder); + this.SetupTransactionProcessorACLContainer(traceFolder); + + // Cache the ports + this.EstateManagementPort = this.EstateManagementContainer.ToHostExposedEndpoint("5000/tcp").Port; + this.TransactionProcessorPort = this.TransactionProcessorContainer.ToHostExposedEndpoint("5002/tcp").Port; + this.TransactionProcessorACLPort = this.TransactionProcessorACLContainer.ToHostExposedEndpoint("5003/tcp").Port; + this.EventStorePort = this.EventStoreContainer.ToHostExposedEndpoint("2113/tcp").Port; + + // Setup the base address resolver + Func estateManagementBaseAddressResolver = api => $"http://127.0.0.1:{this.EstateManagementPort}"; + Func transactionProcessorACLBaseAddressResolver = api => $"http://127.0.0.1:{this.TransactionProcessorACLPort}"; + + HttpClient httpClient = new HttpClient(); + this.EstateClient = new EstateClient(estateManagementBaseAddressResolver, httpClient); + + // TODO: Use this to talk to txn processor scl until we have a client + this.HttpClient = new HttpClient(); + this.HttpClient.BaseAddress = new Uri(transactionProcessorACLBaseAddressResolver(String.Empty)); + } + + public async Task StopContainersForScenarioRun() + { + try + { + if (this.TransactionProcessorACLContainer != null) + { + this.TransactionProcessorACLContainer.StopOnDispose = true; + this.TransactionProcessorACLContainer.RemoveOnDispose = true; + this.TransactionProcessorACLContainer.Dispose(); + } + + if (this.TransactionProcessorContainer != null) + { + this.TransactionProcessorContainer.StopOnDispose = true; + this.TransactionProcessorContainer.RemoveOnDispose = true; + this.TransactionProcessorContainer.Dispose(); + } + + if (this.EstateManagementContainer != null) + { + this.EstateManagementContainer.StopOnDispose = true; + this.EstateManagementContainer.RemoveOnDispose = true; + this.EstateManagementContainer.Dispose(); + } + + if (this.EventStoreContainer != null) + { + this.EventStoreContainer.StopOnDispose = true; + this.EventStoreContainer.RemoveOnDispose = true; + this.EventStoreContainer.Dispose(); + } + + if (this.TestNetwork != null) + { + this.TestNetwork.Stop(); + this.TestNetwork.Remove(true); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private void SetupEstateManagementContainer(String traceFolder) + { + // Management API Container + this.EstateManagementContainer = new Builder() + .UseContainer() + .WithName(this.EstateManagementContainerName) + .WithEnvironment(this.EventStoreConnectionString) //, + //"AppSettings:MigrateDatabase=true", + //"EventStoreSettings:START_PROJECTIONS=true", + //"EventStoreSettings:ContinuousProjectionsFolder=/app/projections/continuous") + .WithCredential("https://www.docker.com", "stuartferguson", "Sc0tland") + .UseImage("stuartferguson/estatemanagement") + .ExposePort(5000) + .UseNetwork(new List { this.TestNetwork, Setup.DatabaseServerNetwork }.ToArray()) + .Mount(traceFolder, "/home", MountType.ReadWrite) + .Build() + .Start().WaitForPort("5000/tcp", 30000); + } + + private void SetupTransactionProcessorContainer(String traceFolder) + { + // Management API Container + this.TransactionProcessorContainer = new Builder() + .UseContainer() + .WithName(this.TransactionProcessorContainerName) + .WithEnvironment(this.EventStoreConnectionString) //, + //"AppSettings:MigrateDatabase=true", + //"EventStoreSettings:START_PROJECTIONS=true", + //"EventStoreSettings:ContinuousProjectionsFolder=/app/projections/continuous") + .UseImage("stuartferguson/transactionprocessor") + .ExposePort(5002) + .UseNetwork(new List { this.TestNetwork, Setup.DatabaseServerNetwork }.ToArray()) + .Mount(traceFolder, "/home", MountType.ReadWrite) + .Build() + .Start().WaitForPort("5002/tcp", 30000); + } + + private void SetupTransactionProcessorACLContainer(String traceFolder) + { + // Management API Container + this.TransactionProcessorACLContainer = new Builder() + .UseContainer() + .WithName(this.TransactionProcessorACLContainerName) + .UseImage("transactionprocessoracl") + .ExposePort(5003) + .UseNetwork(new List { this.TestNetwork }.ToArray()) + .Mount(traceFolder, "/home", MountType.ReadWrite) + .Build() + .Start().WaitForPort("5003/tcp", 30000); + } + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs new file mode 100644 index 0000000..93322f0 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using System.Threading.Tasks; + using TechTalk.SpecFlow; + + [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() + { + String scenarioName = this.ScenarioContext.ScenarioInfo.Title.Replace(" ", ""); + this.TestingContext.DockerHelper = new DockerHelper(); + await this.TestingContext.DockerHelper.StartContainersForScenarioRun(scenarioName).ConfigureAwait(false); + } + + [AfterScenario()] + public async Task StopSystem() + { + await this.TestingContext.DockerHelper.StopContainersForScenarioRun().ConfigureAwait(false); + } + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/Retry.cs b/TransactionProcessor.IntegrationTests/Common/Retry.cs new file mode 100644 index 0000000..a9d3f3d --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/Retry.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using System.Threading; + + public static class Retry + { + #region Fields + + /// + /// The default retry for + /// + private static readonly TimeSpan DefaultRetryFor = TimeSpan.FromSeconds(60); + + /// + /// The default retry interval + /// + private static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(5); + + #endregion + + #region Methods + + /// + /// Fors the specified action. + /// + /// The action. + /// The retry for. + /// The retry interval. + /// + public static async Task For(Func action, + TimeSpan? retryFor = null, + TimeSpan? retryInterval = null) + { + DateTime startTime = DateTime.Now; + Exception lastException = null; + + if (retryFor == null) + { + retryFor = Retry.DefaultRetryFor; + } + + while (DateTime.Now.Subtract(startTime).TotalMilliseconds < retryFor.Value.TotalMilliseconds) + { + try + { + await action().ConfigureAwait(false); + lastException = null; + break; + } + catch (Exception e) + { + lastException = e; + + // wait before retrying + Thread.Sleep(retryInterval ?? Retry.DefaultRetryInterval); + } + } + + if (lastException != null) + { + throw lastException; + } + } + + #endregion + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/Setup.cs b/TransactionProcessor.IntegrationTests/Common/Setup.cs new file mode 100644 index 0000000..6e9e645 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/Setup.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using System.Data; + using System.IO; + using System.Net; + using System.Threading; + using Ductus.FluentDocker.Builders; + using Ductus.FluentDocker.Services; + using Ductus.FluentDocker.Services.Extensions; + using Microsoft.Data.SqlClient; + using Shouldly; + using TechTalk.SpecFlow; + + [Binding] + public class Setup + { + public static IContainerService DatabaseServerContainer; + private static String DbConnectionStringWithNoDatabase; + public static INetworkService DatabaseServerNetwork; + + [BeforeTestRun] + protected static void GlobalSetup() + { + ShouldlyConfiguration.DefaultTaskTimeout = TimeSpan.FromMinutes(1); + + // Setup a network for the DB Server + DatabaseServerNetwork = new Builder().UseNetwork($"sharednetwork").ReuseIfExist().Build(); + + // Start the Database Server here + DbConnectionStringWithNoDatabase = StartMySqlContainerWithOpenConnection(); + } + + public static String GetConnectionString(String databaseName) + { + return $"{DbConnectionStringWithNoDatabase} database={databaseName};"; + } + + private static String StartMySqlContainerWithOpenConnection() + { + String containerName = $"shareddatabasesqlserver"; + DatabaseServerContainer = new Ductus.FluentDocker.Builders.Builder() + .UseContainer() + .WithName(containerName) + .WithCredential("https://docker.io", "stuartferguson", "Sc0tland") + .UseImage("stuartferguson/subscriptionservicedatabasesqlserver") + .WithEnvironment("ACCEPT_EULA=Y", $"SA_PASSWORD=thisisalongpassword123!") + .ExposePort(1433) + .UseNetwork(DatabaseServerNetwork) + .KeepContainer() + .KeepRunning() + .ReuseIfExists() + .Build() + .Start() + .WaitForPort("1433/tcp", 30000); + + IPEndPoint sqlServerEndpoint = DatabaseServerContainer.ToHostExposedEndpoint("1433/tcp"); + + // Try opening a connection + Int32 maxRetries = 10; + Int32 counter = 1; + + String server = "127.0.0.1"; + String database = "SubscriptionServiceConfiguration"; + String user = "sa"; + String password = "thisisalongpassword123!"; + String port = sqlServerEndpoint.Port.ToString(); + + String connectionString = $"server={server},{port};user id={user}; password={password}; database={database};"; + + SqlConnection connection = new SqlConnection(connectionString); + + using (StreamWriter sw = new StreamWriter("C:\\Temp\\testlog.log", true)) + { + while (counter <= maxRetries) + { + try + { + sw.WriteLine($"Attempt {counter}"); + sw.WriteLine(DateTime.Now); + + connection.Open(); + + SqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM EventStoreServers"; + command.ExecuteNonQuery(); + + sw.WriteLine("Connection Opened"); + + connection.Close(); + + break; + } + catch (SqlException ex) + { + if (connection.State == ConnectionState.Open) + { + connection.Close(); + } + + sw.WriteLine(ex); + Thread.Sleep(20000); + } + finally + { + counter++; + } + } + } + + return $"server={containerName};user id={user}; password={password};"; + } + } +} diff --git a/TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs b/TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs new file mode 100644 index 0000000..186b2c4 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using TechTalk.SpecFlow; + + 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/TransactionProcessor.IntegrationTests/Common/TestingContext.cs b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs new file mode 100644 index 0000000..3786f15 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Common/TestingContext.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Common +{ + using DataTransferObjects; + + 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; } + } +} diff --git a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature new file mode 100644 index 0000000..2a3ddb0 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature @@ -0,0 +1,29 @@ +@base @shared +Feature: LogonTransaction + +Background: + Given I have created the following estates + | EstateName | + | Test Estate 1 | + | Test Estate 2 | + + 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 | + +@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 | + + # TODO: Add in once the logon flow is implemented + #Then transaction response should contain the following information + #| TransactionNumber | ResponseCode | ResponseMessage | + #| 1 | 0000 | SUCCESS | + #| 2 | 0000 | SUCCESS | + #| 3 | 0000 | SUCCESS | diff --git a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs new file mode 100644 index 0000000..0a3aba8 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs @@ -0,0 +1,225 @@ +// ------------------------------------------------------------------------------ +// +// 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 TransactionProcessor.IntegrationTests.LogonTransaction +{ + 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")] + public partial class LogonTransactionFeature : Xunit.IClassFixture, System.IDisposable + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private string[] _featureTags = new string[] { + "base", + "shared"}; + + private Xunit.Abstractions.ITestOutputHelper _testOutputHelper; + +#line 1 "LogonTransaction.feature" +#line hidden + + public LogonTransactionFeature(LogonTransactionFeature.FixtureData fixtureData, InternalSpecFlow.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"), "LogonTransaction", null, ProgrammingLanguage.CSharp, new string[] { + "base", + "shared"}); + 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[] { + "EstateName"}); + table1.AddRow(new string[] { + "Test Estate 1"}); + table1.AddRow(new string[] { + "Test Estate 2"}); +#line 5 + testRunner.Given("I have created the following estates", ((string)(null)), table1, "Given "); +#line hidden + TechTalk.SpecFlow.Table table2 = new TechTalk.SpecFlow.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "Country", + "ContactName", + "EmailAddress", + "EstateName"}); + table2.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1"}); + table2.AddRow(new string[] { + "Test Merchant 2", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 2", + "testcontact2@merchant2.co.uk", + "Test Estate 1"}); + table2.AddRow(new string[] { + "Test Merchant 3", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 3", + "testcontact3@merchant2.co.uk", + "Test Estate 2"}); +#line 10 + testRunner.Given("I create the following merchants", ((string)(null)), table2, "Given "); +#line hidden + } + + void System.IDisposable.Dispose() + { + this.TestTearDown(); + } + + [Xunit.SkippableFactAttribute(DisplayName="Logon Transaction")] + [Xunit.TraitAttribute("FeatureTitle", "LogonTransaction")] + [Xunit.TraitAttribute("Description", "Logon Transaction")] + [Xunit.TraitAttribute("Category", "PRTest")] + public virtual void LogonTransaction() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Logon Transaction", null, new string[] { + "PRTest"}); +#line 17 +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 table3 = new TechTalk.SpecFlow.Table(new string[] { + "DateTime", + "TransactionNumber", + "TransactionType", + "MerchantName", + "IMEINumber"}); + table3.AddRow(new string[] { + "Today", + "1", + "Logon", + "Test Merchant 1", + "123456789"}); + table3.AddRow(new string[] { + "Today", + "2", + "Logon", + "Test Merchant 2", + "123456789"}); + table3.AddRow(new string[] { + "Today", + "3", + "Logon", + "Test Merchant 3", + "123456789"}); +#line 18 + testRunner.When("I perform the following transactions", ((string)(null)), table3, "When "); +#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() + { + LogonTransactionFeature.FeatureSetup(); + } + + void System.IDisposable.Dispose() + { + LogonTransactionFeature.FeatureTearDown(); + } + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs new file mode 100644 index 0000000..bc65c0d --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessor.IntegrationTests.Shared +{ + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Common; + using DataTransferObjects; + using EstateManagement.DataTransferObjects.Requests; + using EstateManagement.DataTransferObjects.Responses; + using Newtonsoft.Json; + using Shouldly; + using TechTalk.SpecFlow; + using TransactionProcessorACL.DataTransferObjects; + + [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(@"I have created the following estates")] + [When(@"I create the following estates")] + public async Task WhenICreateTheFollowingEstates(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + + CreateEstateRequest createEstateRequest = new CreateEstateRequest + { + EstateId = Guid.NewGuid(), + EstateName = estateName + }; + + CreateEstateResponse response = await this.TestingContext.DockerHelper.EstateClient.CreateEstate(String.Empty, createEstateRequest, CancellationToken.None).ConfigureAwait(false); + + response.ShouldNotBeNull(); + response.EstateId.ShouldNotBe(Guid.Empty); + + // Cache the estate id + this.TestingContext.Estates.Add(estateName, response.EstateId); + } + + foreach (TableRow tableRow in table.Rows) + { + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + + KeyValuePair estateItem = this.TestingContext.Estates.SingleOrDefault(e => e.Key == estateName); + + estateItem.Key.ShouldNotBeNullOrEmpty(); + estateItem.Value.ShouldNotBe(Guid.Empty); + + EstateResponse estate = await this.TestingContext.DockerHelper.EstateClient.GetEstate(String.Empty, estateItem.Value, CancellationToken.None).ConfigureAwait(false); + + estate.EstateName.ShouldBe(estateName); + } + } + + [Given(@"I have created the following operators")] + [When(@"I create the following operators")] + public async Task WhenICreateTheFollowingOperators(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String operatorName = SpecflowTableHelper.GetStringRowValue(tableRow, "OperatorName"); + Boolean requireCustomMerchantNumber = SpecflowTableHelper.GetBooleanValue(tableRow, "RequireCustomMerchantNumber"); + Boolean requireCustomTerminalNumber = SpecflowTableHelper.GetBooleanValue(tableRow, "RequireCustomTerminalNumber"); + + CreateOperatorRequest createOperatorRequest = new CreateOperatorRequest + { + Name = operatorName, + RequireCustomMerchantNumber = requireCustomMerchantNumber, + RequireCustomTerminalNumber = requireCustomTerminalNumber + }; + + // lookup the estate id based on the name in the table + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + Guid estateId = this.TestingContext.Estates.Single(e => e.Key == estateName).Value; + + CreateOperatorResponse response = await this.TestingContext.DockerHelper.EstateClient.CreateOperator(String.Empty, estateId, createOperatorRequest, CancellationToken.None).ConfigureAwait(false); + + response.ShouldNotBeNull(); + response.EstateId.ShouldNotBe(Guid.Empty); + response.OperatorId.ShouldNotBe(Guid.Empty); + + // Cache the estate id + this.TestingContext.Operators.Add(operatorName, response.OperatorId); + } + } + + [Given("I create the following merchants")] + [When(@"I create the following merchants")] + public async Task WhenICreateTheFollowingMerchants(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String merchantName = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantName"); + CreateMerchantRequest createMerchantRequest = new CreateMerchantRequest + { + Name = merchantName, + Contact = new Contact + { + ContactName = SpecflowTableHelper.GetStringRowValue(tableRow, "ContactName"), + EmailAddress = SpecflowTableHelper.GetStringRowValue(tableRow, "EmailAddress") + }, + Address = new Address + { + AddressLine1 = SpecflowTableHelper.GetStringRowValue(tableRow, "AddressLine1"), + Town = SpecflowTableHelper.GetStringRowValue(tableRow, "Town"), + Region = SpecflowTableHelper.GetStringRowValue(tableRow, "Region"), + Country = SpecflowTableHelper.GetStringRowValue(tableRow, "Country") + } + }; + + // lookup the estate id based on the name in the table + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + Guid estateId = this.TestingContext.Estates.Single(e => e.Key == estateName).Value; + + CreateMerchantResponse response = await this.TestingContext.DockerHelper.EstateClient + .CreateMerchant(String.Empty, estateId, createMerchantRequest, CancellationToken.None).ConfigureAwait(false); + + response.ShouldNotBeNull(); + response.EstateId.ShouldBe(estateId); + response.MerchantId.ShouldNotBe(Guid.Empty); + + // Cache the merchant id + this.TestingContext.Merchants.Add(merchantName, response.MerchantId); + if (this.TestingContext.EstateMerchants.ContainsKey(estateId)) + { + List merchantIdList = this.TestingContext.EstateMerchants[estateId]; + merchantIdList.Add(response.MerchantId); + } + else + { + this.TestingContext.EstateMerchants.Add(estateId, new List { response.MerchantId }); + } + } + + foreach (TableRow tableRow in table.Rows) + { + String estateName = SpecflowTableHelper.GetStringRowValue(tableRow, "EstateName"); + + KeyValuePair estateItem = this.TestingContext.Estates.SingleOrDefault(e => e.Key == estateName); + + estateItem.Key.ShouldNotBeNullOrEmpty(); + estateItem.Value.ShouldNotBe(Guid.Empty); + + String merchantName = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantName"); + + KeyValuePair merchantItem = this.TestingContext.Merchants.SingleOrDefault(m => m.Key == merchantName); + + merchantItem.Key.ShouldNotBeNullOrEmpty(); + merchantItem.Value.ShouldNotBe(Guid.Empty); + + MerchantResponse merchant = await this.TestingContext.DockerHelper.EstateClient.GetMerchant(String.Empty, estateItem.Value, merchantItem.Value, CancellationToken.None).ConfigureAwait(false); + + merchant.MerchantName.ShouldBe(merchantName); + } + } + + [When(@"I assign the following operator to the merchants")] + public async Task WhenIAssignTheFollowingOperatorToTheMerchants(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + // Lookup the merchant id + String merchantName = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantName"); + Guid merchantId = this.TestingContext.Merchants[merchantName]; + + // Lookup the operator id + String operatorName = SpecflowTableHelper.GetStringRowValue(tableRow, "OperatorName"); + Guid operatorId = this.TestingContext.Operators[operatorName]; + + // Now find the estate Id + Guid estateId = this.TestingContext.EstateMerchants.Where(e => e.Value.Contains(merchantId)).Single().Key; + + AssignOperatorRequest assignOperatorRequest = new AssignOperatorRequest + { + OperatorId = operatorId, + MerchantNumber = SpecflowTableHelper.GetStringRowValue(tableRow, "MerchantNumber"), + TerminalNumber = SpecflowTableHelper.GetStringRowValue(tableRow, "TerminalNumber"), + }; + + AssignOperatorResponse assignOperatorResponse = await this.TestingContext.DockerHelper.EstateClient.AssignOperatorToMerchant(String.Empty, estateId, merchantId, assignOperatorRequest, CancellationToken.None).ConfigureAwait(false); + + assignOperatorResponse.EstateId.ShouldBe(estateId); + assignOperatorResponse.MerchantId.ShouldBe(merchantId); + assignOperatorResponse.OperatorId.ShouldBe(operatorId); + } + } + + [When(@"I perform the following transactions")] + public async Task WhenIPerformTheFollowingTransactions(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + // TODO: Get a token for the merchant somehow... + + String dateString = SpecflowTableHelper.GetStringRowValue(tableRow, "DateTime"); + DateTime transactionDateTime = SpecflowTableHelper.GetDateForDateString(dateString, DateTime.Today); + String transactionNumber = SpecflowTableHelper.GetStringRowValue(tableRow, "TransactionNumber"); + String transactionType = SpecflowTableHelper.GetStringRowValue(tableRow, "TransactionType"); + String imeiNumber = SpecflowTableHelper.GetStringRowValue(tableRow, "IMEINumber"); + + switch (transactionType) + { + case "Logon": + await this.PerformLogonTransaction(transactionDateTime, + transactionType, + transactionNumber, + imeiNumber, + CancellationToken.None); + break; + + } + } + } + + private async Task PerformLogonTransaction(DateTime transactionDateTime, String transactionType, String transactionNumber, String imeiNumber, CancellationToken cancellationToken) + { + LogonTransactionRequestMessage logonTransactionRequestMessage = new LogonTransactionRequestMessage + { + IMEINumber = imeiNumber, + TransactionDateTime = transactionDateTime, + TransactionNumber = transactionNumber, + RequireConfigurationInResponse = true + }; + + String uri = "api/transactions"; + + StringContent content = new StringContent(JsonConvert.SerializeObject(logonTransactionRequestMessage, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.All + }), Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await this.TestingContext.DockerHelper.HttpClient.PostAsync(uri, content, cancellationToken); + + response.IsSuccessStatusCode.ShouldBeTrue(); + } + + [Then(@"transaction response should contain the following information")] + public void ThenTransactionResponseShouldContainTheFollowingInformation(Table table) + { + foreach (TableRow tableRow in table.Rows) + { + String transactionNumber = SpecflowTableHelper.GetStringRowValue(tableRow, "TransactionNumber"); + SerialisedMessage serialisedMessage = this.TestingContext.TransactionResponses[transactionNumber]; + Object transactionResponse = JsonConvert.DeserializeObject(serialisedMessage.SerialisedData, + new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.All + }); + this.ValidateTransactionResponse((dynamic)transactionResponse, tableRow); + } + } + + private void ValidateTransactionResponse(LogonTransactionResponse logonTransactionResponse, + TableRow tableRow) + { + String expectedResponseCode = SpecflowTableHelper.GetStringRowValue(tableRow, "ResponseCode"); + String expectedResponseMessage = SpecflowTableHelper.GetStringRowValue(tableRow, "ResponseMessage"); + + logonTransactionResponse.ResponseCode.ShouldBe(expectedResponseCode); + logonTransactionResponse.ResponseMessage.ShouldBe(expectedResponseMessage); + } + + + } +} diff --git a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj new file mode 100644 index 0000000..bf0fd29 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs new file mode 100644 index 0000000..917da9f --- /dev/null +++ b/TransactionProcessorACL.BusinessLogic/RequestHandlers/ProcessLogonTransactionRequestHandler.cs @@ -0,0 +1,20 @@ +namespace TransactionProcessorACL.BusinessLogic.RequestHandlers +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using MediatR; + using Models; + using Requests; + + public class ProcessLogonTransactionRequestHandler : IRequestHandler + { + public async Task Handle(ProcessLogonTransactionRequest request, CancellationToken cancellationToken) + { + return new ProcessLogonTransactionResponse + { + ResponseCode = "1234" + }; + } + } +} diff --git a/TransactionProcessorACL.BusinessLogic/Requests/ProcessLogonTransactionRequest.cs b/TransactionProcessorACL.BusinessLogic/Requests/ProcessLogonTransactionRequest.cs new file mode 100644 index 0000000..09ce23b --- /dev/null +++ b/TransactionProcessorACL.BusinessLogic/Requests/ProcessLogonTransactionRequest.cs @@ -0,0 +1,122 @@ +namespace TransactionProcessorACL.BusinessLogic.Requests +{ + using System; + using MediatR; + using Models; + + /// + /// + /// + /// + public class ProcessLogonTransactionRequest : IRequest + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The estate identifier. + /// The merchant identifier. + /// The transaction date time. + /// The transaction number. + /// The imei number. + /// if set to true [require configuration in response]. + private ProcessLogonTransactionRequest(Guid estateId, + Guid merchantId, + DateTime transactionDateTime, + String transactionNumber, + String imeiNumber, + Boolean requireConfigurationInResponse) + { + this.EstateId = estateId; + this.MerchantId = merchantId; + this.ImeiNumber = imeiNumber; + this.RequireConfigurationInResponse = requireConfigurationInResponse; + this.TransactionDateTime = transactionDateTime; + this.TransactionNumber = transactionNumber; + } + + public ProcessLogonTransactionRequest() + { + // This constructor is only required for IoC tests :| + } + + #endregion + + #region Properties + + /// + /// Gets the estate identifier. + /// + /// + /// The estate identifier. + /// + public Guid EstateId { get; } + + /// + /// Gets the imei number. + /// + /// + /// The imei number. + /// + public String ImeiNumber { get; } + + /// + /// Gets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + public Guid MerchantId { get; } + + /// + /// Gets a value indicating whether [require configuration in response]. + /// + /// + /// true if [require configuration in response]; otherwise, false. + /// + public Boolean RequireConfigurationInResponse { get; } + + /// + /// Gets the transaction date time. + /// + /// + /// The transaction date time. + /// + public DateTime TransactionDateTime { get; } + + /// + /// Gets the transaction number. + /// + /// + /// The transaction number. + /// + public String TransactionNumber { get; } + + #endregion + + #region Methods + + /// + /// Creates the specified estate identifier. + /// + /// The estate identifier. + /// The merchant identifier. + /// The transaction date time. + /// The transaction number. + /// The imei number. + /// if set to true [require configuration in response]. + /// + public static ProcessLogonTransactionRequest Create(Guid estateId, + Guid merchantId, + DateTime transactionDateTime, + String transactionNumber, + String imeiNumber, + Boolean requireConfigurationInResponse) + { + return new ProcessLogonTransactionRequest(estateId, merchantId, transactionDateTime, transactionNumber, imeiNumber, requireConfigurationInResponse); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessorACL.BusinessLogic/TransactionProcessorACL.BusinessLogic.csproj b/TransactionProcessorACL.BusinessLogic/TransactionProcessorACL.BusinessLogic.csproj new file mode 100644 index 0000000..24832e0 --- /dev/null +++ b/TransactionProcessorACL.BusinessLogic/TransactionProcessorACL.BusinessLogic.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + + + + + + + + + + + diff --git a/TransactionProcessorACL.DataTransferObjects/LogonTransactionRequestMessage.cs b/TransactionProcessorACL.DataTransferObjects/LogonTransactionRequestMessage.cs new file mode 100644 index 0000000..8c87f74 --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/LogonTransactionRequestMessage.cs @@ -0,0 +1,9 @@ +namespace TransactionProcessorACL.DataTransferObjects +{ + using System; + + public class LogonTransactionRequestMessage : TransactionRequestMessage + { + public Boolean RequireConfigurationInResponse { get; set; } + } +} \ No newline at end of file diff --git a/TransactionProcessorACL.DataTransferObjects/SaleTransactionRequestMessage.cs b/TransactionProcessorACL.DataTransferObjects/SaleTransactionRequestMessage.cs new file mode 100644 index 0000000..3bb871e --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/SaleTransactionRequestMessage.cs @@ -0,0 +1,9 @@ +namespace TransactionProcessorACL.DataTransferObjects +{ + using System; + + public class SaleTransactionRequestMessage : TransactionRequestMessage + { + public Decimal Amount { get; set; } + } +} \ No newline at end of file diff --git a/TransactionProcessorACL.DataTransferObjects/TransactionProcessorACL.DataTransferObjects.csproj b/TransactionProcessorACL.DataTransferObjects/TransactionProcessorACL.DataTransferObjects.csproj new file mode 100644 index 0000000..47b6ce2 --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/TransactionProcessorACL.DataTransferObjects.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.1 + + + + + + + diff --git a/TransactionProcessorACL.DataTransferObjects/TransactionRequestMessage.cs b/TransactionProcessorACL.DataTransferObjects/TransactionRequestMessage.cs new file mode 100644 index 0000000..2153f65 --- /dev/null +++ b/TransactionProcessorACL.DataTransferObjects/TransactionRequestMessage.cs @@ -0,0 +1,11 @@ +namespace TransactionProcessorACL.DataTransferObjects +{ + using System; + + public class TransactionRequestMessage + { + public String IMEINumber { get; set; } + public DateTime TransactionDateTime { get; set; } + public String TransactionNumber { get; set; } + } +} \ No newline at end of file diff --git a/TransactionProcessorACL.Models/ProcessLogonTransactionResponse.cs b/TransactionProcessorACL.Models/ProcessLogonTransactionResponse.cs new file mode 100644 index 0000000..f01773d --- /dev/null +++ b/TransactionProcessorACL.Models/ProcessLogonTransactionResponse.cs @@ -0,0 +1,23 @@ +using System; + +namespace TransactionProcessorACL.Models +{ + public class ProcessLogonTransactionResponse + { + /// + /// Gets or sets the response code. + /// + /// + /// The response code. + /// + public String ResponseCode { get; set; } + + /// + /// Gets or sets the response message. + /// + /// + /// The response message. + /// + public String ResponseMessage { get; set; } + } +} diff --git a/TransactionProcessorACL.Models/TransactionProcessorACL.Models.csproj b/TransactionProcessorACL.Models/TransactionProcessorACL.Models.csproj new file mode 100644 index 0000000..ea83d29 --- /dev/null +++ b/TransactionProcessorACL.Models/TransactionProcessorACL.Models.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.0 + + + diff --git a/TransactionProcessorACL.Tests/General/BootstrapperTests.cs b/TransactionProcessorACL.Tests/General/BootstrapperTests.cs new file mode 100644 index 0000000..ffd7681 --- /dev/null +++ b/TransactionProcessorACL.Tests/General/BootstrapperTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TransactionProcessorACL.Tests.General +{ + using System.Diagnostics; + using System.Linq; + using Autofac; + using Autofac.Core; + using Autofac.Extensions.DependencyInjection; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Xunit; + + public class BootstrapperTests + { + #region Methods + + /// + /// Verifies the bootstrapper is valid. + /// + [Fact] + public void VerifyBootstrapperIsValid() + { + Mock hostingEnvironment = new Mock(); + hostingEnvironment.Setup(he => he.EnvironmentName).Returns("Development"); + hostingEnvironment.Setup(he => he.ContentRootPath).Returns("/home"); + hostingEnvironment.Setup(he => he.ApplicationName).Returns("Test Application"); + + IServiceCollection services = new ServiceCollection(); + Startup s = new Startup(hostingEnvironment.Object); + s.ConfigureServices(services); + + //Startup.Configuration = this.SetupMemoryConfiguration(); + this.AddTestRegistrations(services, hostingEnvironment.Object); + + ContainerBuilder builder = new ContainerBuilder(); + builder.Populate(services); + + s.ConfigureContainer(builder); + + IContainer container = builder.Build(); + + using(ILifetimeScope scope = container.BeginLifetimeScope()) + { + scope.ResolveAll(new List()); + } + } + + //private IConfigurationRoot SetupMemoryConfiguration() + //{ + // Dictionary configuration = new Dictionary(); + + // IConfigurationBuilder builder = new ConfigurationBuilder(); + + // configuration.Add("EventStoreSettings:ConnectionString", "ConnectTo=tcp://admin:changeit@127.0.0.1:1112;VerboseLogging=true;"); + // configuration.Add("EventStoreSettings:ConnectionName", "UnitTestConnection"); + // configuration.Add("EventStoreSettings:HttpPort", "2113"); + // configuration.Add("AppSettings:UseConnectionStringConfig", "false"); + + // builder.AddInMemoryCollection(configuration); + + // return builder.Build(); + //} + + /// + /// Adds the test registrations. + /// + /// The services. + /// The hosting environment. + private void AddTestRegistrations(IServiceCollection services, + IWebHostEnvironment hostingEnvironment) + { + services.AddLogging(); + DiagnosticListener diagnosticSource = new DiagnosticListener(hostingEnvironment.ApplicationName); + services.AddSingleton(diagnosticSource); + services.AddSingleton(diagnosticSource); + services.AddSingleton(hostingEnvironment); + } + + #endregion + } + + public static class ScopeExtensions + { + #region Methods + + /// + /// Filters the specified ignored assemblies. + /// + /// The services. + /// The ignored assemblies. + /// + public static IList Filter(this IEnumerable services, + IEnumerable ignoredAssemblies) + { + return services.Where(serviceWithType => ignoredAssemblies.All(ignored => ignored != serviceWithType.ServiceType.FullName)).ToList(); + } + + /// + /// Resolves all. + /// + /// The scope. + /// The ignored assemblies. + /// + public static IList ResolveAll(this ILifetimeScope scope, + IEnumerable ignoredAssemblies) + { + var services = scope.ComponentRegistry.Registrations.SelectMany(x => x.Services).OfType().Filter(ignoredAssemblies).ToList(); + + foreach (var serviceWithType in services) + { + try + { + scope.Resolve(serviceWithType.ServiceType); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + return services.Select(x => x.ServiceType).Select(scope.Resolve).ToList(); + } + + #endregion + } +} diff --git a/TransactionProcessorACL.Tests/TransactionProcessorACL.Tests.csproj b/TransactionProcessorACL.Tests/TransactionProcessorACL.Tests.csproj new file mode 100644 index 0000000..fb33efe --- /dev/null +++ b/TransactionProcessorACL.Tests/TransactionProcessorACL.Tests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/TransactionProcessorACL.sln b/TransactionProcessorACL.sln new file mode 100644 index 0000000..164bdf7 --- /dev/null +++ b/TransactionProcessorACL.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL", "TransactionProcessorACL\TransactionProcessorACL.csproj", "{E4C9177E-C366-4236-9CE2-234C259A41BF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AC782193-CA63-4F5C-91B0-CE0E9802DE3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.DataTransferObjects", "TransactionProcessorACL.DataTransferObjects\TransactionProcessorACL.DataTransferObjects.csproj", "{E407F793-58BE-4028-947A-2DAB4D5378EC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.BusinessLogic", "TransactionProcessorACL.BusinessLogic\TransactionProcessorACL.BusinessLogic.csproj", "{245E58CB-145A-4EFE-802E-E409955499D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.Models", "TransactionProcessorACL.Models\TransactionProcessorACL.Models.csproj", "{EB2C5C28-064C-4382-AD6F-C01C6EB9615D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F4C740F9-3213-4C68-9DFA-1B1ECCEE647E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.Tests", "TransactionProcessorACL.Tests\TransactionProcessorACL.Tests.csproj", "{4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.IntegrationTests", "TransactionProcessor.IntegrationTests\TransactionProcessor.IntegrationTests.csproj", "{721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4C9177E-C366-4236-9CE2-234C259A41BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4C9177E-C366-4236-9CE2-234C259A41BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4C9177E-C366-4236-9CE2-234C259A41BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4C9177E-C366-4236-9CE2-234C259A41BF}.Release|Any CPU.Build.0 = Release|Any CPU + {E407F793-58BE-4028-947A-2DAB4D5378EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E407F793-58BE-4028-947A-2DAB4D5378EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E407F793-58BE-4028-947A-2DAB4D5378EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E407F793-58BE-4028-947A-2DAB4D5378EC}.Release|Any CPU.Build.0 = Release|Any CPU + {245E58CB-145A-4EFE-802E-E409955499D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {245E58CB-145A-4EFE-802E-E409955499D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {245E58CB-145A-4EFE-802E-E409955499D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {245E58CB-145A-4EFE-802E-E409955499D3}.Release|Any CPU.Build.0 = Release|Any CPU + {EB2C5C28-064C-4382-AD6F-C01C6EB9615D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB2C5C28-064C-4382-AD6F-C01C6EB9615D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB2C5C28-064C-4382-AD6F-C01C6EB9615D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB2C5C28-064C-4382-AD6F-C01C6EB9615D}.Release|Any CPU.Build.0 = Release|Any CPU + {4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}.Release|Any CPU.Build.0 = Release|Any CPU + {721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E4C9177E-C366-4236-9CE2-234C259A41BF} = {AC782193-CA63-4F5C-91B0-CE0E9802DE3E} + {E407F793-58BE-4028-947A-2DAB4D5378EC} = {AC782193-CA63-4F5C-91B0-CE0E9802DE3E} + {245E58CB-145A-4EFE-802E-E409955499D3} = {AC782193-CA63-4F5C-91B0-CE0E9802DE3E} + {EB2C5C28-064C-4382-AD6F-C01C6EB9615D} = {AC782193-CA63-4F5C-91B0-CE0E9802DE3E} + {4D4F3A13-B3F8-4859-9C29-F7003EE83E9A} = {F4C740F9-3213-4C68-9DFA-1B1ECCEE647E} + {721B2A8F-0BC6-4EB8-9300-E848BF58DA1C} = {F4C740F9-3213-4C68-9DFA-1B1ECCEE647E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D7348E19-B39B-4769-834E-D70D74676E81} + EndGlobalSection +EndGlobal diff --git a/TransactionProcessorACL/Common/ConfigureSwaggerOptions.cs b/TransactionProcessorACL/Common/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..ec3faca --- /dev/null +++ b/TransactionProcessorACL/Common/ConfigureSwaggerOptions.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace TransactionProcessorACL.Common +{ + using System.Diagnostics.CodeAnalysis; + using Microsoft.AspNetCore.Mvc.ApiExplorer; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using Microsoft.OpenApi.Models; + using Swashbuckle.AspNetCore.SwaggerGen; + + /// + /// Configures the Swagger generation options. + /// + /// This allows API versioning to define a Swagger document per API version after the + /// service has been resolved from the service container. + [ExcludeFromCodeCoverage] + public class ConfigureSwaggerOptions : IConfigureOptions + { + #region Fields + + /// + /// The provider + /// + private readonly IApiVersionDescriptionProvider provider; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The provider used to generate Swagger documents. + public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider; + + #endregion + + #region Methods + + /// + public void Configure(SwaggerGenOptions options) + { + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach (ApiVersionDescription description in this.provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, ConfigureSwaggerOptions.CreateInfoForApiVersion(description)); + } + } + + /// + /// Creates the information for API version. + /// + /// The description. + /// + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + OpenApiInfo info = new OpenApiInfo + { + Title = "Golf Handicapping API", + Version = description.ApiVersion.ToString(), + Description = "A REST Api to manage the golf club handicapping system.", + Contact = new OpenApiContact + { + Name = "Stuart Ferguson", + Email = "golfhandicapping@btinternet.com" + }, + License = new OpenApiLicense + { + Name = "TODO" + } + }; + + if (description.IsDeprecated) + { + info.Description += " This API version has been deprecated."; + } + + return info; + } + + #endregion + } +} diff --git a/TransactionProcessorACL/Common/SwaggerDefaultValues.cs b/TransactionProcessorACL/Common/SwaggerDefaultValues.cs new file mode 100644 index 0000000..9c57981 --- /dev/null +++ b/TransactionProcessorACL/Common/SwaggerDefaultValues.cs @@ -0,0 +1,52 @@ +namespace TransactionProcessorACL.Common +{ + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Abstractions; + using Microsoft.AspNetCore.Mvc.ApiExplorer; + using Microsoft.AspNetCore.Mvc.Versioning; + using Microsoft.OpenApi.Models; + using Swashbuckle.AspNetCore.SwaggerGen; + + /// + /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. + /// + /// This is only required due to bugs in the . + /// Once they are fixed and published, this class can be removed. + [ExcludeFromCodeCoverage] + public class SwaggerDefaultValues : IOperationFilter + { + /// + /// Applies the filter to the specified operation using the given context. + /// + /// The operation to apply the filter to. + /// The current operation filter context. + public void Apply(OpenApiOperation operation, + OperationFilterContext context) + { + ApiDescription apiDescription = context.ApiDescription; + ApiVersion apiVersion = apiDescription.GetApiVersion(); + ApiVersionModel model = apiDescription.ActionDescriptor.GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit); + + operation.Deprecated = model.DeprecatedApiVersions.Contains(apiVersion); + + if (operation.Parameters == null) + { + return; + } + + foreach (OpenApiParameter parameter in operation.Parameters) + { + ApiParameterDescription description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + if (parameter.Description == null) + { + parameter.Description = description.ModelMetadata?.Description; + } + + parameter.Required |= description.IsRequired; + } + } + } +} \ No newline at end of file diff --git a/TransactionProcessorACL/Common/SwaggerJsonConverter.cs b/TransactionProcessorACL/Common/SwaggerJsonConverter.cs new file mode 100644 index 0000000..56e76dc --- /dev/null +++ b/TransactionProcessorACL/Common/SwaggerJsonConverter.cs @@ -0,0 +1,76 @@ +namespace TransactionProcessorACL.Common +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + [ExcludeFromCodeCoverage] + public class SwaggerJsonConverter : JsonConverter + { + #region Properties + + /// + /// Gets a value indicating whether this can read JSON. + /// + /// + /// true if this can read JSON; otherwise, false. + /// + public override Boolean CanRead => false; + + #endregion + + #region Methods + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override Boolean CanConvert(Type objectType) + { + return true; + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// + /// The object value. + /// + /// + /// + public override Object ReadJson(JsonReader reader, + Type objectType, + Object existingValue, + JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, + Object value, + JsonSerializer serializer) + { + // Disable sending the $type in the serialized json + serializer.TypeNameHandling = TypeNameHandling.None; + + JToken t = JToken.FromObject(value); + t.WriteTo(writer); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessorACL/Controllers/TransactionController.cs b/TransactionProcessorACL/Controllers/TransactionController.cs new file mode 100644 index 0000000..5ddbf0b --- /dev/null +++ b/TransactionProcessorACL/Controllers/TransactionController.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TransactionProcessorACL.Controllers +{ + using System.Diagnostics.CodeAnalysis; + using System.Threading; + using BusinessLogic.Requests; + using DataTransferObjects; + using MediatR; + using Microsoft.AspNetCore.Mvc; + using Newtonsoft.Json; + + [ExcludeFromCodeCoverage] + [Route(TransactionController.ControllerRoute)] + [ApiController] + [ApiVersion("1.0")] + public class TransactionController : ControllerBase + { + private readonly IMediator Mediator; + + public TransactionController(IMediator mediator) + { + this.Mediator = mediator; + } + + [HttpPost] + [Route("")] + public async Task PerformTransaction([FromBody] TransactionRequestMessage transactionRequest, + CancellationToken cancellationToken) + { + var request = this.CreateCommandFromRequest((dynamic)transactionRequest); + var response = await this.Mediator.Send(request, cancellationToken); + + return this.Ok(); + // TODO: Populate the GET route + //return this.Created("", transactionResponse); + } + + private ProcessLogonTransactionRequest CreateCommandFromRequest(LogonTransactionRequestMessage logonTransactionRequestMessage) + { + Guid estateId = Guid.Empty; + Guid merchantId = Guid.Empty; + ProcessLogonTransactionRequest request = ProcessLogonTransactionRequest.Create(estateId, + merchantId, + logonTransactionRequestMessage.TransactionDateTime, + logonTransactionRequestMessage.TransactionNumber, + logonTransactionRequestMessage.IMEINumber, + logonTransactionRequestMessage.RequireConfigurationInResponse); + + return request; + } + + + #region Others + + /// + /// The controller name + /// + public const String ControllerName = "transactions"; + + /// + /// The controller route + /// + private const String ControllerRoute = "api/" + TransactionController.ControllerName; + + #endregion + } +} diff --git a/TransactionProcessorACL/Dockerfile b/TransactionProcessorACL/Dockerfile new file mode 100644 index 0000000..b398fc1 --- /dev/null +++ b/TransactionProcessorACL/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build +WORKDIR /src +COPY ["TransactionProcessorACL/TransactionProcessorACL.csproj", "TransactionProcessorACL/"] +COPY ["TransactionProcessorACL/NuGet.Config", "TransactionProcessorACL/"] +COPY ["TransactionProcessorACL.DataTransferObjects/TransactionProcessorACL.DataTransferObjects.csproj", "TransactionProcessorACL.DataTransferObjects/"] +COPY ["TransactionProcessorACL.BusinessLogic/TransactionProcessorACL.BusinessLogic.csproj", "TransactionProcessorACL.BusinessLogic/"] +COPY ["TransactionProcessorACL.Models/TransactionProcessorACL.Models.csproj", "TransactionProcessorACL.Models/"] +RUN dotnet restore "TransactionProcessorACL/TransactionProcessorACL.csproj" +COPY . . +WORKDIR "/src/TransactionProcessorACL" +RUN dotnet build "TransactionProcessorACL.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "TransactionProcessorACL.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "TransactionProcessorACL.dll"] \ No newline at end of file diff --git a/TransactionProcessorACL/NuGet.Config b/TransactionProcessorACL/NuGet.Config new file mode 100644 index 0000000..c6bc4ff --- /dev/null +++ b/TransactionProcessorACL/NuGet.Config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TransactionProcessorACL/Program.cs b/TransactionProcessorACL/Program.cs new file mode 100644 index 0000000..3b006ac --- /dev/null +++ b/TransactionProcessorACL/Program.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace TransactionProcessorACL +{ + using System.IO; + using Autofac.Extensions.DependencyInjection; + + public class Program + { + public static void Main(string[] args) + { + Program.CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + Console.Title = "Transaction Processor ACL"; + + //At this stage, we only need our hosting file for ip and ports + IConfigurationRoot config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("hosting.json", optional: true) + .AddJsonFile("hosting.development.json", optional: true) + .AddEnvironmentVariables().Build(); + + IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args); + hostBuilder.ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.UseConfiguration(config); + webBuilder.UseKestrel(); + }).UseServiceProviderFactory(new AutofacServiceProviderFactory()); + return hostBuilder; + } + } +} diff --git a/TransactionProcessorACL/Startup.cs b/TransactionProcessorACL/Startup.cs new file mode 100644 index 0000000..0ea4bf6 --- /dev/null +++ b/TransactionProcessorACL/Startup.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace TransactionProcessorACL +{ + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Reflection; + using Autofac; + using BusinessLogic.RequestHandlers; + using BusinessLogic.Requests; + using Common; + using MediatR; + using Microsoft.AspNetCore.Mvc.ApiExplorer; + using Microsoft.AspNetCore.Mvc.Versioning; + using Microsoft.Extensions.Options; + using Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + using NLog.Extensions.Logging; + using Shared.Extensions; + using Shared.General; + using Swashbuckle.AspNetCore.Filters; + using Swashbuckle.AspNetCore.SwaggerGen; + + [ExcludeFromCodeCoverage] + public class Startup + { + public Startup(IWebHostEnvironment webHostEnvironment) + { + IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(webHostEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true).AddEnvironmentVariables(); + + Startup.Configuration = builder.Build(); + Startup.WebHostEnvironment = webHostEnvironment; + } + + public static IConfigurationRoot Configuration { get; set; } + + public static IWebHostEnvironment WebHostEnvironment { get; set; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + this.ConfigureMiddlewareServices(services); + + //services.AddSingleton(); + services.AddTransient(); + } + + private void ConfigureMiddlewareServices(IServiceCollection services) + { + services.AddApiVersioning( + options => + { + // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + options.DefaultApiVersion = new ApiVersion(1, 0); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionReader = new HeaderApiVersionReader("api-version"); + }); + + services.AddVersionedApiExplorer( + options => + { + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + options.GroupNameFormat = "'v'VVV"; + + // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // can also be used to control the format of the API version in route templates + options.SubstituteApiVersionInUrl = true; + }); + + services.AddTransient, ConfigureSwaggerOptions>(); + + services.AddSwaggerGen(c => + { + // add a custom operation filter which sets default values + c.OperationFilter(); + c.ExampleFilters(); + c.SchemaGeneratorOptions.GeneratePolymorphicSchemas = true; + }); + + services.AddSwaggerExamplesFromAssemblyOf(); + + services.AddControllers().AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto; + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }); + + Assembly assembly = this.GetType().GetTypeInfo().Assembly; + services.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices(); + } + + public void ConfigureContainer(ContainerBuilder builder) + { + // request & notification handlers + builder.Register(context => + { + var c = context.Resolve(); + return t => c.Resolve(t); + }); + + builder.RegisterType().As>().SingleInstance(); + builder.RegisterType().As>().SingleInstance(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, + IApiVersionDescriptionProvider provider) + { + String nlogConfigFilename = "nlog.config"; + + if (env.IsDevelopment()) + { + nlogConfigFilename = $"nlog.{env.EnvironmentName}.config"; + app.UseDeveloperExceptionPage(); + } + + loggerFactory.ConfigureNLog(Path.Combine(env.ContentRootPath, nlogConfigFilename)); + loggerFactory.AddNLog(); + + ILogger logger = loggerFactory.CreateLogger("TransactionProcessor"); + + Logger.Initialise(logger); + + ConfigurationReader.Initialise(Startup.Configuration); + + app.AddRequestLogging(); + app.AddResponseLogging(); + app.AddExceptionHandler(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + app.UseSwagger(); + + app.UseSwaggerUI( + options => + { + // build a swagger endpoint for each discovered API version + foreach (ApiVersionDescription description in provider.ApiVersionDescriptions) + { + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); + } + }); + } + } +} diff --git a/TransactionProcessorACL/TransactionProcessorACL.csproj b/TransactionProcessorACL/TransactionProcessorACL.csproj new file mode 100644 index 0000000..9c36bea --- /dev/null +++ b/TransactionProcessorACL/TransactionProcessorACL.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.0 + Linux + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TransactionProcessorACL/appsettings.json b/TransactionProcessorACL/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/TransactionProcessorACL/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/TransactionProcessorACL/hosting.json b/TransactionProcessorACL/hosting.json new file mode 100644 index 0000000..08a4a36 --- /dev/null +++ b/TransactionProcessorACL/hosting.json @@ -0,0 +1,3 @@ +{ + "urls": "http://*:5003" +} \ No newline at end of file diff --git a/TransactionProcessorACL/nlog.config b/TransactionProcessorACL/nlog.config new file mode 100644 index 0000000..b400f6c --- /dev/null +++ b/TransactionProcessorACL/nlog.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/TransactionProcessorACL/nlog.development.config b/TransactionProcessorACL/nlog.development.config new file mode 100644 index 0000000..82b2dfb --- /dev/null +++ b/TransactionProcessorACL/nlog.development.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file From c3e6dfd9fc305d52090abde696d400b81ab36301 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 6 Dec 2019 15:44:36 +0000 Subject: [PATCH 2/3] Workflow updates --- .github/release-drafter.yml | 13 +++ .github/workflows/createrelease.yml | 119 +++++++++++++++++++++++ .github/workflows/nightlybuild.yml | 52 ++++++++++ .github/workflows/pullrequest.yml | 39 ++++++++ .github/workflows/release-management.yml | 16 +++ 5 files changed, 239 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/createrelease.yml create mode 100644 .github/workflows/nightlybuild.yml create mode 100644 .github/workflows/pullrequest.yml create mode 100644 .github/workflows/release-management.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..62aef73 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,13 @@ +categories: + - title: '🚀 Features' + labels: + - 'feature' + - title: '🐛 Bug Fixes' + labels: + - 'bug' + - title: '🧰 Maintenance' + label: 'maintenance' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +template: | + ## Changes + $CHANGES diff --git a/.github/workflows/createrelease.yml b/.github/workflows/createrelease.yml new file mode 100644 index 0000000..c16d86d --- /dev/null +++ b/.github/workflows/createrelease.yml @@ -0,0 +1,119 @@ +name: Release + +on: + release: + types: [published] + +jobs: + build: + name: "Release" + env: + ASPNETCORE_ENVIRONMENT: "Production" + + runs-on: ubuntu-16.04 + + steps: + - uses: actions/checkout@v1 + + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + + - name: Setup .NET Core 3.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.100 + + - name: Restore Nuget Packages + run: dotnet restore TransactionProcessorACL.sln --source https://api.nuget.org/v3/index.json --source https://www.myget.org/F/transactionprocessing/api/v3/index.json + + - name: Build Code + run: dotnet build TransactionProcessorACL.sln --configuration Release + + - name: Run Unit Tests + run: | + echo "ASPNETCORE_ENVIRONMENT are > ${ASPNETCORE_ENVIRONMENT}" + dotnet test "TransactionProcessorACL.Tests\TransactionProcessorACL.Tests.csproj" + + - name: Build Docker Images + run: | + docker build . --file TransactionProcessorACL/Dockerfile --tag transactionprocessoracl:latest --tag stuartferguson/transactionprocessoracl:latest --tag stuartferguson/transactionprocessoracl:${{ steps.get_version.outputs.VERSION }} + + - name: Run Integration Tests + run: dotnet test "TransactionProcessorACL.IntegrationTests\TransactionProcessorACL.IntegrationTests.csproj" + + - name: Publish Images to Docker Hub + run: | + docker login --username=${{ secrets.DOCKER_USERNAME }} --password=${{ secrets.DOCKER_PASSWORD }} + docker push stuartferguson/transactionprocessor:latest + docker push stuartferguson/transactionprocessor:${{ steps.get_version.outputs.VERSION }} + + - name: Publish API + run: dotnet publish "TransactionProcessorACL\TransactionProcessorACL.csproj" --configuration Release --output publishOutput + + - name: Setup .NET Core 2.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 2.0.0 + + - name: Extract Octopus Tools + run: | + mkdir /opt/octo + cd /opt/octo + wget -O /opt/octo/octopus.zip https://download.octopusdeploy.com/octopus-tools/6.12.0/OctopusTools.6.12.0.portable.zip + unzip /opt/octo/octopus.zip + chmod +x /opt/octo/Octo + + - name: Pack Files for Octopus + run: >- + /opt/octo/Octo pack + --outFolder /home/runner/work/TransactionProcessorACL/TransactionProcessorACL + --basePath /home/runner/work/TransactionProcessorACL/TransactionProcessorACL/publishOutput + --id TransactionProcessorACL + --version ${{ steps.get_version.outputs.VERSION }} + --format zip + --verbose + --logLevel=verbose + + - name: Push Package to Octopus + run: >- + /opt/octo/Octo push + --server http://sferguson.ddns.net:9001 + --apiKey API-UTN58QCF8HSATACNUBY41XPUC + --package /home/runner/work/TransactionProcessorACL/TransactionProcessorACL/TransactionProcessorACL.${{ steps.get_version.outputs.VERSION }}.zip + --overwrite-mode IgnoreIfExists + + - name: Get Release + id: getrelease + uses: octokit/request-action@v1.x + with: + route: GET /repos/StuartFerguson/TransactionProcessorACL/releases/tags/${{ steps.get_version.outputs.VERSION }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Release Notes + id: buildreleasenotes + uses: gr2m/get-json-paths-action@v1.x + with: + json: ${{ steps.getrelease.outputs.data }} + releasenote: "body" + + - name: Create & Deploy Release in Octopus + run: >- + /opt/octo/Octo create-release + --server http://sferguson.ddns.net:9001 + --apiKey API-UTN58QCF8HSATACNUBY41XPUC + --project "Transaction Processor ACL" + --version ${{ steps.get_version.outputs.VERSION }} + --channel Default + --deployTo Development + --waitfordeployment + --deploymenttimeout 00:05:00 + --releasenotes "${{ steps.buildreleasenotes.outputs.releasenote }}" + + - name: Setup .NET Core 3.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.100 + + diff --git a/.github/workflows/nightlybuild.yml b/.github/workflows/nightlybuild.yml new file mode 100644 index 0000000..200a264 --- /dev/null +++ b/.github/workflows/nightlybuild.yml @@ -0,0 +1,52 @@ +name: Nightly Build + +on: + schedule: + - cron: "* 23 * * *" + +jobs: + build: + name: "Nightly Build" + env: + ASPNETCORE_ENVIRONMENT: "Production" + + runs-on: ubuntu-16.04 + + steps: + - uses: actions/checkout@v1 + + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.100 + + - name: Restore Nuget Packages + run: dotnet restore TransactionProcessorACL.sln --source https://api.nuget.org/v3/index.json --source https://www.myget.org/F/transactionprocessing/api/v3/index.json + + - name: Build Code + run: dotnet build TransactionProcessorACL.sln --configuration Release + + - name: Run Unit Tests + run: | + echo "ASPNETCORE_ENVIRONMENT are > ${ASPNETCORE_ENVIRONMENT}" + dotnet test "TransactionProcessorACL.Tests\TransactionProcessorACL.Tests.csproj" /p:CollectCoverage=true /p:Exclude="[xunit*]*" /p:ExcludeByAttribute="Obsolete" /p:ExcludeByAttribute="GeneratedCodeAttribute" /p:ExcludeByAttribute="CompilerGeneratedAttribute" /p:ExcludeByAttribute="ExcludeFromCodeCoverageAttribute" /p:CoverletOutput="../lcov.info" /maxcpucount:1 /p:CoverletOutputFormat="lcov" + + - name: Setup Node.js for use with actions + uses: actions/setup-node@v1.1.0 + + - name: Combine Coverage + run: | + npm i lcov-result-merger + ./node_modules/lcov-result-merger/bin/lcov-result-merger.js '*.info' 'lcov.info' + + - name: Upload Code Coverage + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + path-to-lcov: lcov.info + + - name: Build Docker Image + run: docker build . --file TransactionProcessorACL/Dockerfile --tag transactionprocessoracl:latest + + - name: Run Integration Tests + run: dotnet test "TransactionProcessorACL.IntegrationTests\TransactionProcessorACL.IntegrationTests.csproj" \ No newline at end of file diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml new file mode 100644 index 0000000..dd13490 --- /dev/null +++ b/.github/workflows/pullrequest.yml @@ -0,0 +1,39 @@ +name: Build and Test Pull Requests + +on: + pull_request: + branches: + - master + +jobs: + build: + name: "Build and Test Pull Requests" + env: + ASPNETCORE_ENVIRONMENT: "Production" + + runs-on: ubuntu-16.04 + + steps: + - uses: actions/checkout@v1 + + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.0.100 + + - name: Restore Nuget Packages + run: dotnet restore TransactionProcessorACL.sln --source https://api.nuget.org/v3/index.json --source https://www.myget.org/F/transactionprocessing/api/v3/index.json + + - name: Build Code + run: dotnet build TransactionProcessorACL.sln --configuration Release + + - name: Run Unit Tests + run: | + echo "ASPNETCORE_ENVIRONMENT are > ${ASPNETCORE_ENVIRONMENT}" + dotnet test "TransactionProcessorACL.Tests\TransactionProcessorACL.Tests.csproj" + + - name: Build Docker Image + run: docker build . --file TransactionProcessorACL/Dockerfile --tag transactionprocessoracl:latest + + - name: Run Integration Tests + run: dotnet test "TransactionProcessorACL.IntegrationTests\TransactionProcessorACL.IntegrationTests.csproj" --filter Category=PRTest diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml new file mode 100644 index 0000000..83d8d98 --- /dev/null +++ b/.github/workflows/release-management.yml @@ -0,0 +1,16 @@ +name: Release Management + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_draft_release: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: toolmantim/release-drafter@v5.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5c34a145fb88e4c2c0c6d80331ea980d0ebc5266 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Fri, 6 Dec 2019 15:54:01 +0000 Subject: [PATCH 3/3] Fix specflow project file crime --- .../Common/DockerHelper.cs | 0 .../Common/GenericSteps.cs | 0 .../Common/Retry.cs | 0 .../Common/Setup.cs | 0 .../Common/SpecflowTableHelper.cs | 0 .../Common/TestingContext.cs | 0 .../LogonTransaction/LogonTransaction.feature | 0 .../LogonTransaction/LogonTransaction.feature.cs | 2 +- .../Shared/SharedSteps.cs | 0 .../TransactionProcessorACL.IntegrationTests.csproj | 0 TransactionProcessorACL.sln | 5 ++--- 11 files changed, 3 insertions(+), 4 deletions(-) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/DockerHelper.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/GenericSteps.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/Retry.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/Setup.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/SpecflowTableHelper.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Common/TestingContext.cs (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/LogonTransaction/LogonTransaction.feature (100%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/LogonTransaction/LogonTransaction.feature.cs (99%) rename {TransactionProcessor.IntegrationTests => TransactionProcessorACL.IntegrationTests}/Shared/SharedSteps.cs (100%) rename TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj => TransactionProcessorACL.IntegrationTests/TransactionProcessorACL.IntegrationTests.csproj (100%) diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/DockerHelper.cs rename to TransactionProcessorACL.IntegrationTests/Common/DockerHelper.cs diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/GenericSteps.cs rename to TransactionProcessorACL.IntegrationTests/Common/GenericSteps.cs diff --git a/TransactionProcessor.IntegrationTests/Common/Retry.cs b/TransactionProcessorACL.IntegrationTests/Common/Retry.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/Retry.cs rename to TransactionProcessorACL.IntegrationTests/Common/Retry.cs diff --git a/TransactionProcessor.IntegrationTests/Common/Setup.cs b/TransactionProcessorACL.IntegrationTests/Common/Setup.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/Setup.cs rename to TransactionProcessorACL.IntegrationTests/Common/Setup.cs diff --git a/TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs b/TransactionProcessorACL.IntegrationTests/Common/SpecflowTableHelper.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/SpecflowTableHelper.cs rename to TransactionProcessorACL.IntegrationTests/Common/SpecflowTableHelper.cs diff --git a/TransactionProcessor.IntegrationTests/Common/TestingContext.cs b/TransactionProcessorACL.IntegrationTests/Common/TestingContext.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Common/TestingContext.cs rename to TransactionProcessorACL.IntegrationTests/Common/TestingContext.cs diff --git a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature b/TransactionProcessorACL.IntegrationTests/LogonTransaction/LogonTransaction.feature similarity index 100% rename from TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature rename to TransactionProcessorACL.IntegrationTests/LogonTransaction/LogonTransaction.feature diff --git a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs b/TransactionProcessorACL.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs similarity index 99% rename from TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs rename to TransactionProcessorACL.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs index 0a3aba8..5efd21f 100644 --- a/TransactionProcessor.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs +++ b/TransactionProcessorACL.IntegrationTests/LogonTransaction/LogonTransaction.feature.cs @@ -10,7 +10,7 @@ // ------------------------------------------------------------------------------ #region Designer generated code #pragma warning disable -namespace TransactionProcessor.IntegrationTests.LogonTransaction +namespace TransactionProcessorACL.IntegrationTests.LogonTransaction { using TechTalk.SpecFlow; using System; diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs similarity index 100% rename from TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs rename to TransactionProcessorACL.IntegrationTests/Shared/SharedSteps.cs diff --git a/TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj b/TransactionProcessorACL.IntegrationTests/TransactionProcessorACL.IntegrationTests.csproj similarity index 100% rename from TransactionProcessor.IntegrationTests/TransactionProcessor.IntegrationTests.csproj rename to TransactionProcessorACL.IntegrationTests/TransactionProcessorACL.IntegrationTests.csproj diff --git a/TransactionProcessorACL.sln b/TransactionProcessorACL.sln index 164bdf7..0052a26 100644 --- a/TransactionProcessorACL.sln +++ b/TransactionProcessorACL.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29509.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -17,7 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F4C740F9-3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.Tests", "TransactionProcessorACL.Tests\TransactionProcessorACL.Tests.csproj", "{4D4F3A13-B3F8-4859-9C29-F7003EE83E9A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionProcessor.IntegrationTests", "TransactionProcessor.IntegrationTests\TransactionProcessor.IntegrationTests.csproj", "{721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransactionProcessorACL.IntegrationTests", "TransactionProcessorACL.IntegrationTests\TransactionProcessorACL.IntegrationTests.csproj", "{721B2A8F-0BC6-4EB8-9300-E848BF58DA1C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution