From 5ae63378483b90401361fdcfff0bdd63f1cabeaf Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 22:27:16 +0100 Subject: [PATCH 01/13] Added Happy Path integration test around existing OrderService --- TechTest/AddCustomers.sql | 17 ++++ TechTest/AddOrders.sql | 15 ++++ .../AnyCompany.IntegrationTests.csproj | 83 +++++++++++++++++++ .../AnyCompany.IntegrationTests/App.config | 7 ++ .../DataHelpers/CustomerDataHelper.cs | 26 ++++++ .../DataHelpers/OrderDataHelper.cs | 26 ++++++ .../OrderServiceIntegrationTests.cs | 47 +++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++++++ .../packages.config | 7 ++ .../AnyCompany.Tests/AnyCompany.Tests.csproj | 56 +++++++++---- TechTest/AnyCompany.Tests/packages.config | 8 ++ TechTest/AnyCompany/Customer.cs | 2 + TechTest/AnyCompany/OrderRepository.cs | 1 + TechTest/TechTest.sln | 25 +++++- 14 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 TechTest/AddCustomers.sql create mode 100644 TechTest/AddOrders.sql create mode 100644 TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj create mode 100644 TechTest/AnyCompany.IntegrationTests/App.config create mode 100644 TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs create mode 100644 TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs create mode 100644 TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs create mode 100644 TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs create mode 100644 TechTest/AnyCompany.IntegrationTests/packages.config create mode 100644 TechTest/AnyCompany.Tests/packages.config diff --git a/TechTest/AddCustomers.sql b/TechTest/AddCustomers.sql new file mode 100644 index 0000000..8aae7ae --- /dev/null +++ b/TechTest/AddCustomers.sql @@ -0,0 +1,17 @@ +USE [Customers] +GO + +-- The initial customer table +IF (NOT EXISTS (SELECT * + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' + AND TABLE_NAME = 'Customer')) +BEGIN + CREATE TABLE [dbo].[Customer] + ( + [CustomerId] INT NOT NULL PRIMARY KEY IDENTITY(1,1), + [Name] NVARCHAR(100) NOT NULL, + [Country] VARCHAR(2) NOT NULL, + [DateOfBirth] DATETIME NOT NULL + ) +END \ No newline at end of file diff --git a/TechTest/AddOrders.sql b/TechTest/AddOrders.sql new file mode 100644 index 0000000..24a0a92 --- /dev/null +++ b/TechTest/AddOrders.sql @@ -0,0 +1,15 @@ +USE [Orders] +GO + +IF (NOT EXISTS (SELECT * + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' + AND TABLE_NAME = 'Orders')) +BEGIN + CREATE TABLE [dbo].[Orders] + ( + [OrderId] INT NOT NULL PRIMARY KEY, + [Amount] DECIMAL NOT NULL, + [VAT] DECIMAL NOT NULL + ) +END diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj new file mode 100644 index 0000000..35ac085 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -0,0 +1,83 @@ + + + + + + Debug + AnyCPU + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91} + Library + Properties + AnyCompany.IntegrationTests + AnyCompany.IntegrationTests + v4.6.1 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Dapper.1.60.6\lib\net451\Dapper.dll + + + ..\packages\Dapper.Extension.1.0.0.1\lib\net45\Dapper.Extension.dll + + + ..\packages\DapperExtensions.1.6.3\lib\net45\DapperExtensions.dll + + + ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} + AnyCompany + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/App.config b/TechTest/AnyCompany.IntegrationTests/App.config new file mode 100644 index 0000000..a9be8d4 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs b/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs new file mode 100644 index 0000000..d7ee3f8 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs @@ -0,0 +1,26 @@ +using System.Configuration; +using System.Data.SqlClient; +using DapperExtensions; + +namespace AnyCompany.IntegrationTests.DataHelpers +{ + public static class CustomerDataHelper + { + public static Customer Add(Customer customer) + { + using (var connection = new SqlConnection(GetConnectionString())) + { + connection.Open(); + int id = connection.Insert(customer); + + customer.CustomerId = id; + return customer; + } + } + + private static string GetConnectionString() + { + return ConfigurationManager.ConnectionStrings["CustomerConnectionString"].ConnectionString; + } + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs new file mode 100644 index 0000000..2fac2bb --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs @@ -0,0 +1,26 @@ +using System.Configuration; +using System.Data.SqlClient; +using System.Linq; +using Dapper; + +namespace AnyCompany.IntegrationTests.DataHelpers +{ + public static class OrderDataHelper + { + public static Order GetOrder(int id) + { + using (var connection = new SqlConnection(GetConnectionString())) + { + connection.Open(); + return connection.Query( + "SELECT * FROM dbo.Orders WHERE OrderId = @OrderId", + new { OrderId = id }).First(); + } + } + + private static string GetConnectionString() + { + return ConfigurationManager.ConnectionStrings["OrderConnectionString"].ConnectionString; + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs new file mode 100644 index 0000000..4d22c14 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -0,0 +1,47 @@ +using System; +using AnyCompany.IntegrationTests.DataHelpers; +using NUnit.Framework; + +namespace AnyCompany.IntegrationTests +{ + [TestFixture] + public class OrderServiceIntegrationTests + { + [TestCase("UK")] + [TestCase("FR")] + public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) + { + // Arrange. + var customer = CustomerDataHelper.Add(GetCustomer(country)); + var order = GetOrder(); + var orderService = new OrderService(); + + // Act. + var result = orderService.PlaceOrder(order, customer.CustomerId); + var insertedOrder = OrderDataHelper.GetOrder(order.OrderId); + + // Assert. + Assert.IsTrue(result); + Assert.IsNotNull(insertedOrder); + } + + private Order GetOrder() + { + return new Order + { + OrderId = new Random().Next(1, 2000000), // generate a random Id since the OrderId column is not an IDENTITY column + Amount = 200.99 + }; + } + + private Customer GetCustomer(string country) + { + return new Customer + { + Country = country, + DateOfBirth = DateTime.UtcNow.AddDays(-20), + Name = "John Smith" + }; + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..98f6cd7 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyCompany.IntegrationTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.IntegrationTests")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("df80d016-3afe-43b3-bc8f-3cec313b4c91")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.IntegrationTests/packages.config b/TechTest/AnyCompany.IntegrationTests/packages.config new file mode 100644 index 0000000..dc96ce7 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index b537fc2..487c8e9 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -1,16 +1,19 @@ - + + Debug AnyCPU - cd5d577e-bdc9-4dfc-ac6a-b1da474995f3 + {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} Library Properties AnyCompany.Tests AnyCompany.Tests v4.6.1 512 + + true @@ -30,24 +33,43 @@ 4 - - - - - - - - - - - - - - + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + + + ..\packages\Moq.4.12.0\lib\net45\Moq.dll + + + ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + + + + + + + + - + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/packages.config b/TechTest/AnyCompany.Tests/packages.config new file mode 100644 index 0000000..bf0c6ae --- /dev/null +++ b/TechTest/AnyCompany.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/Customer.cs b/TechTest/AnyCompany/Customer.cs index aa994b6..359e158 100644 --- a/TechTest/AnyCompany/Customer.cs +++ b/TechTest/AnyCompany/Customer.cs @@ -4,6 +4,8 @@ namespace AnyCompany { public class Customer { + public int CustomerId { get; set; } + public string Country { get; set; } public DateTime DateOfBirth { get; set; } diff --git a/TechTest/AnyCompany/OrderRepository.cs b/TechTest/AnyCompany/OrderRepository.cs index 3229885..f0a0036 100644 --- a/TechTest/AnyCompany/OrderRepository.cs +++ b/TechTest/AnyCompany/OrderRepository.cs @@ -21,5 +21,6 @@ public void Save(Order order) connection.Close(); } + } } diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 1c1c57a..bc5d1cb 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28917.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany", "AnyCompany\AnyCompany.csproj", "{C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}" EndProject @@ -12,6 +12,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Instructions.txt = Instructions.txt EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database Scaffolding Scripts", "Database Scaffolding Scripts", "{0372EDAB-809E-4632-A929-146E339E9EB0}" + ProjectSection(SolutionItems) = preProject + AddCustomers.sql = AddCustomers.sql + AddOrders.sql = AddOrders.sql + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Service Layer", "1. Service Layer", "{25F15798-EF39-43DD-B9A8-11DA2D8EB5EB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Tests", "2. Tests", "{118DE10D-B767-428F-B9D3-3987DF71146A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.IntegrationTests", "AnyCompany.IntegrationTests\AnyCompany.IntegrationTests.csproj", "{DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,10 +38,19 @@ Global {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Release|Any CPU.Build.0 = Release|Any CPU + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} + {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} = {118DE10D-B767-428F-B9D3-3987DF71146A} + {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91} = {118DE10D-B767-428F-B9D3-3987DF71146A} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D629674-23F9-49DA-971E-77A5D7A7C39D} EndGlobalSection From da4e85e9322f607844ebc60df60f5a9bcf9e1778 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 22:37:45 +0100 Subject: [PATCH 02/13] Encapsulated domain models behind service layer using dtos --- .../AnyCompany.IntegrationTests.csproj | 4 ++ .../DataHelpers/CustomerDataHelper.cs | 1 + .../DataHelpers/OrderDataHelper.cs | 1 + .../OrderServiceIntegrationTests.cs | 8 +-- .../AnyCompany.Models.csproj | 49 +++++++++++++++++++ .../Customer.cs | 2 +- .../Order.cs | 2 +- .../Properties/AssemblyInfo.cs | 36 ++++++++++++++ TechTest/AnyCompany/AnyCompany.csproj | 11 ++++- TechTest/AnyCompany/CustomerRepository.cs | 1 + TechTest/AnyCompany/Dtos/OrderDto.cs | 9 ++++ TechTest/AnyCompany/Mappers/OrderMapper.cs | 18 +++++++ TechTest/AnyCompany/OrderRepository.cs | 1 + TechTest/AnyCompany/OrderService.cs | 16 +++--- TechTest/AnyCompany/Services/IOrderService.cs | 9 ++++ TechTest/TechTest.sln | 13 ++++- 16 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 TechTest/AnyCompany.Models/AnyCompany.Models.csproj rename TechTest/{AnyCompany => AnyCompany.Models}/Customer.cs (89%) rename TechTest/{AnyCompany => AnyCompany.Models}/Order.cs (84%) create mode 100644 TechTest/AnyCompany.Models/Properties/AssemblyInfo.cs create mode 100644 TechTest/AnyCompany/Dtos/OrderDto.cs create mode 100644 TechTest/AnyCompany/Mappers/OrderMapper.cs create mode 100644 TechTest/AnyCompany/Services/IOrderService.cs diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj index 35ac085..957e6c1 100644 --- a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -68,6 +68,10 @@ + + {695D4D06-2336-4B91-BA68-4A4924CF4729} + AnyCompany.Models + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} AnyCompany diff --git a/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs b/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs index d7ee3f8..1be92a3 100644 --- a/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs +++ b/TechTest/AnyCompany.IntegrationTests/DataHelpers/CustomerDataHelper.cs @@ -1,5 +1,6 @@ using System.Configuration; using System.Data.SqlClient; +using AnyCompany.Models; using DapperExtensions; namespace AnyCompany.IntegrationTests.DataHelpers diff --git a/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs index 2fac2bb..05a5660 100644 --- a/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs +++ b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs @@ -1,6 +1,7 @@ using System.Configuration; using System.Data.SqlClient; using System.Linq; +using AnyCompany.Models; using Dapper; namespace AnyCompany.IntegrationTests.DataHelpers diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs index 4d22c14..8c5fb8a 100644 --- a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -1,5 +1,7 @@ using System; +using AnyCompany.Dtos; using AnyCompany.IntegrationTests.DataHelpers; +using AnyCompany.Models; using NUnit.Framework; namespace AnyCompany.IntegrationTests @@ -13,7 +15,7 @@ public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) { // Arrange. var customer = CustomerDataHelper.Add(GetCustomer(country)); - var order = GetOrder(); + var order = GetOrderDto(); var orderService = new OrderService(); // Act. @@ -25,9 +27,9 @@ public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) Assert.IsNotNull(insertedOrder); } - private Order GetOrder() + private OrderDto GetOrderDto() { - return new Order + return new OrderDto { OrderId = new Random().Next(1, 2000000), // generate a random Id since the OrderId column is not an IDENTITY column Amount = 200.99 diff --git a/TechTest/AnyCompany.Models/AnyCompany.Models.csproj b/TechTest/AnyCompany.Models/AnyCompany.Models.csproj new file mode 100644 index 0000000..49e3a75 --- /dev/null +++ b/TechTest/AnyCompany.Models/AnyCompany.Models.csproj @@ -0,0 +1,49 @@ + + + + + Debug + AnyCPU + {695D4D06-2336-4B91-BA68-4A4924CF4729} + Library + Properties + AnyCompany.Models + AnyCompany.Models + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/Customer.cs b/TechTest/AnyCompany.Models/Customer.cs similarity index 89% rename from TechTest/AnyCompany/Customer.cs rename to TechTest/AnyCompany.Models/Customer.cs index 359e158..e0fbd3f 100644 --- a/TechTest/AnyCompany/Customer.cs +++ b/TechTest/AnyCompany.Models/Customer.cs @@ -1,6 +1,6 @@ using System; -namespace AnyCompany +namespace AnyCompany.Models { public class Customer { diff --git a/TechTest/AnyCompany/Order.cs b/TechTest/AnyCompany.Models/Order.cs similarity index 84% rename from TechTest/AnyCompany/Order.cs rename to TechTest/AnyCompany.Models/Order.cs index fec8e7b..8887aa7 100644 --- a/TechTest/AnyCompany/Order.cs +++ b/TechTest/AnyCompany.Models/Order.cs @@ -1,4 +1,4 @@ -namespace AnyCompany +namespace AnyCompany.Models { public class Order { diff --git a/TechTest/AnyCompany.Models/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Models/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..71be66d --- /dev/null +++ b/TechTest/AnyCompany.Models/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyCompany.Models")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.Models")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("695d4d06-2336-4b91-ba68-4a4924cf4729")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index 5b0498d..82a2311 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -40,12 +40,19 @@ - - + + + + + + + {695D4D06-2336-4B91-BA68-4A4924CF4729} + AnyCompany.Models + \ No newline at end of file diff --git a/TechTest/AnyCompany/CustomerRepository.cs b/TechTest/AnyCompany/CustomerRepository.cs index e3de9b7..63cbf59 100644 --- a/TechTest/AnyCompany/CustomerRepository.cs +++ b/TechTest/AnyCompany/CustomerRepository.cs @@ -1,5 +1,6 @@ using System; using System.Data.SqlClient; +using AnyCompany.Models; namespace AnyCompany { diff --git a/TechTest/AnyCompany/Dtos/OrderDto.cs b/TechTest/AnyCompany/Dtos/OrderDto.cs new file mode 100644 index 0000000..e1a59a2 --- /dev/null +++ b/TechTest/AnyCompany/Dtos/OrderDto.cs @@ -0,0 +1,9 @@ +namespace AnyCompany.Dtos +{ + public class OrderDto + { + public int OrderId { get; set; } + public double Amount { get; set; } + public double VAT { get; set; } + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany/Mappers/OrderMapper.cs b/TechTest/AnyCompany/Mappers/OrderMapper.cs new file mode 100644 index 0000000..ce4bd88 --- /dev/null +++ b/TechTest/AnyCompany/Mappers/OrderMapper.cs @@ -0,0 +1,18 @@ +using AnyCompany.Dtos; +using AnyCompany.Models; + +namespace AnyCompany.Mappers +{ + public static class OrderMapper + { + public static Order Map(OrderDto orderDto) + { + return new Order + { + OrderId = orderDto.OrderId, + Amount = orderDto.Amount, + VAT = orderDto.VAT + }; + } + } +} diff --git a/TechTest/AnyCompany/OrderRepository.cs b/TechTest/AnyCompany/OrderRepository.cs index f0a0036..1927d7d 100644 --- a/TechTest/AnyCompany/OrderRepository.cs +++ b/TechTest/AnyCompany/OrderRepository.cs @@ -1,4 +1,5 @@ using System.Data.SqlClient; +using AnyCompany.Models; namespace AnyCompany { diff --git a/TechTest/AnyCompany/OrderService.cs b/TechTest/AnyCompany/OrderService.cs index ebfb103..43e9260 100644 --- a/TechTest/AnyCompany/OrderService.cs +++ b/TechTest/AnyCompany/OrderService.cs @@ -1,22 +1,26 @@ -namespace AnyCompany +using AnyCompany.Dtos; +using AnyCompany.Mappers; +using AnyCompany.Models; + +namespace AnyCompany { public class OrderService { private readonly OrderRepository orderRepository = new OrderRepository(); - public bool PlaceOrder(Order order, int customerId) + public bool PlaceOrder(OrderDto orderDto, int customerId) { Customer customer = CustomerRepository.Load(customerId); - if (order.Amount == 0) + if (orderDto.Amount == 0) return false; if (customer.Country == "UK") - order.VAT = 0.2d; + orderDto.VAT = 0.2d; else - order.VAT = 0; + orderDto.VAT = 0; - orderRepository.Save(order); + orderRepository.Save(OrderMapper.Map(orderDto)); return true; } diff --git a/TechTest/AnyCompany/Services/IOrderService.cs b/TechTest/AnyCompany/Services/IOrderService.cs new file mode 100644 index 0000000..98497b0 --- /dev/null +++ b/TechTest/AnyCompany/Services/IOrderService.cs @@ -0,0 +1,9 @@ +using AnyCompany.Dtos; + +namespace AnyCompany.Services +{ + interface IOrderService + { + bool PlaceOrder(OrderDto order, int customerId); + } +} diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index bc5d1cb..4b04d50 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -20,10 +20,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database Scaffolding Script EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Service Layer", "1. Service Layer", "{25F15798-EF39-43DD-B9A8-11DA2D8EB5EB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Tests", "2. Tests", "{118DE10D-B767-428F-B9D3-3987DF71146A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4. Tests", "4. Tests", "{118DE10D-B767-428F-B9D3-3987DF71146A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.IntegrationTests", "AnyCompany.IntegrationTests\AnyCompany.IntegrationTests.csproj", "{DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Data Access Layer", "2. Data Access Layer", "{A38327A3-2391-4469-AB71-3AB3714531BB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Framework", "3. Framework", "{DB29E18C-EE03-4193-857E-FD98D35D783A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Models", "AnyCompany.Models\AnyCompany.Models.csproj", "{695D4D06-2336-4B91-BA68-4A4924CF4729}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +48,10 @@ Global {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91}.Release|Any CPU.Build.0 = Release|Any CPU + {695D4D06-2336-4B91-BA68-4A4924CF4729}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {695D4D06-2336-4B91-BA68-4A4924CF4729}.Debug|Any CPU.Build.0 = Debug|Any CPU + {695D4D06-2336-4B91-BA68-4A4924CF4729}.Release|Any CPU.ActiveCfg = Release|Any CPU + {695D4D06-2336-4B91-BA68-4A4924CF4729}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +60,7 @@ Global {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} = {118DE10D-B767-428F-B9D3-3987DF71146A} {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91} = {118DE10D-B767-428F-B9D3-3987DF71146A} + {695D4D06-2336-4B91-BA68-4A4924CF4729} = {DB29E18C-EE03-4193-857E-FD98D35D783A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D629674-23F9-49DA-971E-77A5D7A7C39D} From 96a729f081e5de7f14826b8f0e1beb02a25135ae Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 22:42:10 +0100 Subject: [PATCH 03/13] Renamed AnyCompany->AnyCompany.Services --- .../AnyCompany.IntegrationTests.csproj | 6 +++--- .../OrderServiceIntegrationTests.cs | 2 +- .../AnyCompany.Services.csproj} | 4 ++-- .../CustomerRepository.cs | 0 .../Dtos/OrderDto.cs | 2 +- .../Mappers/OrderMapper.cs | 6 +++--- .../OrderRepository.cs | 0 .../OrderService.cs | 6 +++--- .../Properties/AssemblyInfo.cs | 0 .../Services/IOrderService.cs | 4 ++-- TechTest/TechTest.sln | 16 ++++++++-------- 11 files changed, 23 insertions(+), 23 deletions(-) rename TechTest/{AnyCompany/AnyCompany.csproj => AnyCompany.Services/AnyCompany.Services.csproj} (95%) rename TechTest/{AnyCompany => AnyCompany.Services}/CustomerRepository.cs (100%) rename TechTest/{AnyCompany => AnyCompany.Services}/Dtos/OrderDto.cs (81%) rename TechTest/{AnyCompany => AnyCompany.Services}/Mappers/OrderMapper.cs (75%) rename TechTest/{AnyCompany => AnyCompany.Services}/OrderRepository.cs (100%) rename TechTest/{AnyCompany => AnyCompany.Services}/OrderService.cs (86%) rename TechTest/{AnyCompany => AnyCompany.Services}/Properties/AssemblyInfo.cs (100%) rename TechTest/{AnyCompany => AnyCompany.Services}/Services/IOrderService.cs (57%) diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj index 957e6c1..1482ddf 100644 --- a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -72,9 +72,9 @@ {695D4D06-2336-4B91-BA68-4A4924CF4729} AnyCompany.Models - - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} - AnyCompany + + {c7e15594-7d8f-4c18-9dd7-14f3fbb1572d} + AnyCompany.Services diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs index 8c5fb8a..32bec3a 100644 --- a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -1,7 +1,7 @@ using System; -using AnyCompany.Dtos; using AnyCompany.IntegrationTests.DataHelpers; using AnyCompany.Models; +using AnyCompany.Services.Dtos; using NUnit.Framework; namespace AnyCompany.IntegrationTests diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj similarity index 95% rename from TechTest/AnyCompany/AnyCompany.csproj rename to TechTest/AnyCompany.Services/AnyCompany.Services.csproj index 82a2311..cc7e18e 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -7,8 +7,8 @@ {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} Library Properties - AnyCompany - AnyCompany + AnyCompany.Services + AnyCompany.Services v4.6.1 512 diff --git a/TechTest/AnyCompany/CustomerRepository.cs b/TechTest/AnyCompany.Services/CustomerRepository.cs similarity index 100% rename from TechTest/AnyCompany/CustomerRepository.cs rename to TechTest/AnyCompany.Services/CustomerRepository.cs diff --git a/TechTest/AnyCompany/Dtos/OrderDto.cs b/TechTest/AnyCompany.Services/Dtos/OrderDto.cs similarity index 81% rename from TechTest/AnyCompany/Dtos/OrderDto.cs rename to TechTest/AnyCompany.Services/Dtos/OrderDto.cs index e1a59a2..01d89c1 100644 --- a/TechTest/AnyCompany/Dtos/OrderDto.cs +++ b/TechTest/AnyCompany.Services/Dtos/OrderDto.cs @@ -1,4 +1,4 @@ -namespace AnyCompany.Dtos +namespace AnyCompany.Services.Dtos { public class OrderDto { diff --git a/TechTest/AnyCompany/Mappers/OrderMapper.cs b/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs similarity index 75% rename from TechTest/AnyCompany/Mappers/OrderMapper.cs rename to TechTest/AnyCompany.Services/Mappers/OrderMapper.cs index ce4bd88..07030ba 100644 --- a/TechTest/AnyCompany/Mappers/OrderMapper.cs +++ b/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs @@ -1,7 +1,7 @@ -using AnyCompany.Dtos; -using AnyCompany.Models; +using AnyCompany.Models; +using AnyCompany.Services.Dtos; -namespace AnyCompany.Mappers +namespace AnyCompany.Services.Mappers { public static class OrderMapper { diff --git a/TechTest/AnyCompany/OrderRepository.cs b/TechTest/AnyCompany.Services/OrderRepository.cs similarity index 100% rename from TechTest/AnyCompany/OrderRepository.cs rename to TechTest/AnyCompany.Services/OrderRepository.cs diff --git a/TechTest/AnyCompany/OrderService.cs b/TechTest/AnyCompany.Services/OrderService.cs similarity index 86% rename from TechTest/AnyCompany/OrderService.cs rename to TechTest/AnyCompany.Services/OrderService.cs index 43e9260..15091de 100644 --- a/TechTest/AnyCompany/OrderService.cs +++ b/TechTest/AnyCompany.Services/OrderService.cs @@ -1,6 +1,6 @@ -using AnyCompany.Dtos; -using AnyCompany.Mappers; -using AnyCompany.Models; +using AnyCompany.Models; +using AnyCompany.Services.Dtos; +using AnyCompany.Services.Mappers; namespace AnyCompany { diff --git a/TechTest/AnyCompany/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Services/Properties/AssemblyInfo.cs similarity index 100% rename from TechTest/AnyCompany/Properties/AssemblyInfo.cs rename to TechTest/AnyCompany.Services/Properties/AssemblyInfo.cs diff --git a/TechTest/AnyCompany/Services/IOrderService.cs b/TechTest/AnyCompany.Services/Services/IOrderService.cs similarity index 57% rename from TechTest/AnyCompany/Services/IOrderService.cs rename to TechTest/AnyCompany.Services/Services/IOrderService.cs index 98497b0..86c3214 100644 --- a/TechTest/AnyCompany/Services/IOrderService.cs +++ b/TechTest/AnyCompany.Services/Services/IOrderService.cs @@ -1,6 +1,6 @@ -using AnyCompany.Dtos; +using AnyCompany.Services.Dtos; -namespace AnyCompany.Services +namespace AnyCompany.Services.Services { interface IOrderService { diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 4b04d50..da160a3 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28917.181 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany", "AnyCompany\AnyCompany.csproj", "{C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Tests", "AnyCompany.Tests\AnyCompany.Tests.csproj", "{CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B6D3C1BB-2A37-4E17-9EE3-DEF28286E782}" @@ -18,7 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database Scaffolding Script AddOrders.sql = AddOrders.sql EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Service Layer", "1. Service Layer", "{25F15798-EF39-43DD-B9A8-11DA2D8EB5EB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Business Layer", "1. Business Layer", "{25F15798-EF39-43DD-B9A8-11DA2D8EB5EB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4. Tests", "4. Tests", "{118DE10D-B767-428F-B9D3-3987DF71146A}" EndProject @@ -30,16 +28,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Framework", "3. Framewor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Models", "AnyCompany.Models\AnyCompany.Models.csproj", "{695D4D06-2336-4B91-BA68-4A4924CF4729}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Services", "AnyCompany.Services\AnyCompany.Services.csproj", "{C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.Build.0 = Release|Any CPU {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -52,15 +48,19 @@ Global {695D4D06-2336-4B91-BA68-4A4924CF4729}.Debug|Any CPU.Build.0 = Debug|Any CPU {695D4D06-2336-4B91-BA68-4A4924CF4729}.Release|Any CPU.ActiveCfg = Release|Any CPU {695D4D06-2336-4B91-BA68-4A4924CF4729}.Release|Any CPU.Build.0 = Release|Any CPU + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} = {118DE10D-B767-428F-B9D3-3987DF71146A} {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91} = {118DE10D-B767-428F-B9D3-3987DF71146A} {695D4D06-2336-4B91-BA68-4A4924CF4729} = {DB29E18C-EE03-4193-857E-FD98D35D783A} + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D629674-23F9-49DA-971E-77A5D7A7C39D} From ae8d01c1d6b16bf06d7baab43686012a2be83eaa Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:17:50 +0100 Subject: [PATCH 04/13] Abstracted existing data layer so we can reimplement them --- .../AnyCompany.Data.Contract.csproj | 55 +++++++++++++ .../Properties/AssemblyInfo.cs | 36 +++++++++ .../Repositories/ICustomerRepository.cs | 9 +++ .../Repositories/IOrderRepository.cs | 10 +++ .../AnyCompany.Data.Dapper.csproj | 60 +++++++++++++++ .../Properties/AssemblyInfo.cs | 36 +++++++++ .../Repositories}/CustomerRepository.cs | 2 +- .../Repositories/CustomerRepositoryWrapper.cs | 15 ++++ .../Repositories}/OrderRepository.cs | 9 ++- .../AnyCompany.IntegrationTests.csproj | 19 +++++ .../Bootstrap/Bootstrapper.cs | 29 +++++++ .../Bootstrap/IntegrationTestInstaller.cs | 25 ++++++ .../OrderServiceIntegrationTests.cs | 13 +++- .../packages.config | 2 + TechTest/AnyCompany.Models/Order.cs | 1 + .../AnyCompany.Services.csproj | 6 +- TechTest/AnyCompany.Services/OrderService.cs | 24 ++++-- .../Services/IOrderService.cs | 2 +- .../AnyCompany.Tests/AnyCompany.Tests.csproj | 16 +++- TechTest/AnyCompany.Tests/Class1.cs | 12 --- .../AnyCompany.Tests/OrderServiceTests.cs | 77 +++++++++++++++++++ TechTest/TechTest.sln | 14 ++++ 22 files changed, 446 insertions(+), 26 deletions(-) create mode 100644 TechTest/AnyCompany.Data.Contract/AnyCompany.Data.Contract.csproj create mode 100644 TechTest/AnyCompany.Data.Contract/Properties/AssemblyInfo.cs create mode 100644 TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs create mode 100644 TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs create mode 100644 TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj create mode 100644 TechTest/AnyCompany.Data.Dapper/Properties/AssemblyInfo.cs rename TechTest/{AnyCompany.Services => AnyCompany.Data.Dapper/Repositories}/CustomerRepository.cs (95%) create mode 100644 TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs rename TechTest/{AnyCompany.Services => AnyCompany.Data.Dapper/Repositories}/OrderRepository.cs (74%) create mode 100644 TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs create mode 100644 TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs delete mode 100644 TechTest/AnyCompany.Tests/Class1.cs create mode 100644 TechTest/AnyCompany.Tests/OrderServiceTests.cs diff --git a/TechTest/AnyCompany.Data.Contract/AnyCompany.Data.Contract.csproj b/TechTest/AnyCompany.Data.Contract/AnyCompany.Data.Contract.csproj new file mode 100644 index 0000000..88f0629 --- /dev/null +++ b/TechTest/AnyCompany.Data.Contract/AnyCompany.Data.Contract.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + Library + Properties + AnyCompany.Data.Contract + AnyCompany.Data.Contract + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + {695D4D06-2336-4B91-BA68-4A4924CF4729} + AnyCompany.Models + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Data.Contract/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Data.Contract/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..326c08d --- /dev/null +++ b/TechTest/AnyCompany.Data.Contract/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyCompany.Data.Contract")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.Data.Contract")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7f4d6121-26ef-48a2-a2f4-bbd5d114d8e7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs b/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs new file mode 100644 index 0000000..f0c2b3f --- /dev/null +++ b/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs @@ -0,0 +1,9 @@ +using AnyCompany.Models; + +namespace AnyCompany.Data.Contract.Repositories +{ + public interface ICustomerRepository + { + Customer Load(int customerId); + } +} diff --git a/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs b/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs new file mode 100644 index 0000000..c14763e --- /dev/null +++ b/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using AnyCompany.Models; + +namespace AnyCompany.Data.Contract.Repositories +{ + public interface IOrderRepository + { + void Add(Order order); + } +} diff --git a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj new file mode 100644 index 0000000..aa5b48d --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} + Library + Properties + AnyCompany.Data.Dapper + AnyCompany.Data.Dapper + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + AnyCompany.Data.Contract + + + {695D4D06-2336-4B91-BA68-4A4924CF4729} + AnyCompany.Models + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Data.Dapper/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Data.Dapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..032cea6 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyCompany.Data.Dapper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.Data.Dapper")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cef90cea-cedf-44b4-9203-9b9adf9a15ce")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.Services/CustomerRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs similarity index 95% rename from TechTest/AnyCompany.Services/CustomerRepository.cs rename to TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs index 63cbf59..064c3eb 100644 --- a/TechTest/AnyCompany.Services/CustomerRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs @@ -2,7 +2,7 @@ using System.Data.SqlClient; using AnyCompany.Models; -namespace AnyCompany +namespace AnyCompany.Data.Dapper.Repositories { public static class CustomerRepository { diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs new file mode 100644 index 0000000..7a03ee8 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs @@ -0,0 +1,15 @@ +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Models; + +namespace AnyCompany.Data.Dapper.Repositories +{ + public class CustomerRepositoryWrapper : ICustomerRepository + { + // using this to act as a proxy to the static CustomerRepository - I assumed this was legacy that + // we're trying to abstract and eventually replace + public Customer Load(int customerId) + { + return CustomerRepository.Load(customerId); + } + } +} diff --git a/TechTest/AnyCompany.Services/OrderRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs similarity index 74% rename from TechTest/AnyCompany.Services/OrderRepository.cs rename to TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs index 1927d7d..32d55ee 100644 --- a/TechTest/AnyCompany.Services/OrderRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs @@ -1,9 +1,10 @@ using System.Data.SqlClient; +using AnyCompany.Data.Contract.Repositories; using AnyCompany.Models; -namespace AnyCompany +namespace AnyCompany.Data.Dapper.Repositories { - internal class OrderRepository + public class OrderRepository : IOrderRepository { private static string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; @@ -23,5 +24,9 @@ public void Save(Order order) connection.Close(); } + public void Add(Order order) + { + Save(order); // TODO just a proxy for now + } } } diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj index 1482ddf..aac9af8 100644 --- a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -34,6 +34,12 @@ 4 + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\Castle.Windsor.5.0.0\lib\net45\Castle.Windsor.dll + ..\packages\Dapper.1.60.6\lib\net451\Dapper.dll @@ -50,6 +56,8 @@ + + @@ -58,6 +66,8 @@ + + @@ -68,6 +78,14 @@ + + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + AnyCompany.Data.Contract + + + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} + AnyCompany.Data.Dapper + {695D4D06-2336-4B91-BA68-4A4924CF4729} AnyCompany.Models @@ -77,6 +95,7 @@ AnyCompany.Services + diff --git a/TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs b/TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs new file mode 100644 index 0000000..6c2979b --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs @@ -0,0 +1,29 @@ +using Castle.Windsor; + +namespace AnyCompany.IntegrationTests.Bootstrap +{ + internal static class Bootstrapper + { + private static object _objLock = new object(); + + private static WindsorContainer _container; + + internal static WindsorContainer GetContainer() + { + if (_container != null) + return _container; + + lock (_objLock) + { + if (_container != null) + return _container; + + var container = new WindsorContainer(); + container.Install(new IntegrationTestInstaller()); + + _container = container; + return _container; + } + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs b/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs new file mode 100644 index 0000000..cceed97 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs @@ -0,0 +1,25 @@ +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Data.Dapper.Repositories; +using AnyCompany.Services; +using AnyCompany.Services.Services; +using Castle.MicroKernel.Registration; +using Castle.MicroKernel.SubSystems.Configuration; +using Castle.Windsor; + +namespace AnyCompany.IntegrationTests.Bootstrap +{ + public class IntegrationTestInstaller : IWindsorInstaller + { + public void Install(IWindsorContainer container, IConfigurationStore store) + { + container.Register( + Component.For()); + + container.Register( + Component.For()); + + container.Register( + Component.For()); + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs index 32bec3a..a5573f6 100644 --- a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -1,7 +1,10 @@ using System; +using AnyCompany.IntegrationTests.Bootstrap; using AnyCompany.IntegrationTests.DataHelpers; using AnyCompany.Models; using AnyCompany.Services.Dtos; +using AnyCompany.Services.Services; +using Castle.Windsor; using NUnit.Framework; namespace AnyCompany.IntegrationTests @@ -9,6 +12,14 @@ namespace AnyCompany.IntegrationTests [TestFixture] public class OrderServiceIntegrationTests { + private IWindsorContainer _container; + + [SetUp] + public void SetupFixture() + { + _container = Bootstrapper.GetContainer(); + } + [TestCase("UK")] [TestCase("FR")] public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) @@ -16,7 +27,7 @@ public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) // Arrange. var customer = CustomerDataHelper.Add(GetCustomer(country)); var order = GetOrderDto(); - var orderService = new OrderService(); + var orderService = _container.Resolve(); // Act. var result = orderService.PlaceOrder(order, customer.CustomerId); diff --git a/TechTest/AnyCompany.IntegrationTests/packages.config b/TechTest/AnyCompany.IntegrationTests/packages.config index dc96ce7..9a9ebea 100644 --- a/TechTest/AnyCompany.IntegrationTests/packages.config +++ b/TechTest/AnyCompany.IntegrationTests/packages.config @@ -1,5 +1,7 @@  + + diff --git a/TechTest/AnyCompany.Models/Order.cs b/TechTest/AnyCompany.Models/Order.cs index 8887aa7..14420a2 100644 --- a/TechTest/AnyCompany.Models/Order.cs +++ b/TechTest/AnyCompany.Models/Order.cs @@ -5,5 +5,6 @@ public class Order public int OrderId { get; set; } public double Amount { get; set; } public double VAT { get; set; } + public int CustomerId { get; set; } } } diff --git a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj index cc7e18e..45efd41 100644 --- a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -40,15 +40,17 @@ - - + + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + AnyCompany.Data.Contract + {695D4D06-2336-4B91-BA68-4A4924CF4729} AnyCompany.Models diff --git a/TechTest/AnyCompany.Services/OrderService.cs b/TechTest/AnyCompany.Services/OrderService.cs index 15091de..f41739f 100644 --- a/TechTest/AnyCompany.Services/OrderService.cs +++ b/TechTest/AnyCompany.Services/OrderService.cs @@ -1,16 +1,25 @@ -using AnyCompany.Models; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Models; using AnyCompany.Services.Dtos; using AnyCompany.Services.Mappers; +using AnyCompany.Services.Services; -namespace AnyCompany +namespace AnyCompany.Services { - public class OrderService + public class OrderService : IOrderService { - private readonly OrderRepository orderRepository = new OrderRepository(); + private readonly IOrderRepository _orderRepository; + private readonly ICustomerRepository _customerRepository; + + public OrderService(IOrderRepository orderRepository, ICustomerRepository customerRepository) + { + _orderRepository = orderRepository; + _customerRepository = customerRepository; + } public bool PlaceOrder(OrderDto orderDto, int customerId) { - Customer customer = CustomerRepository.Load(customerId); + Customer customer = _customerRepository.Load(customerId); if (orderDto.Amount == 0) return false; @@ -20,7 +29,10 @@ public bool PlaceOrder(OrderDto orderDto, int customerId) else orderDto.VAT = 0; - orderRepository.Save(OrderMapper.Map(orderDto)); + var order = OrderMapper.Map(orderDto); + order.CustomerId = customerId; + + _orderRepository.Add(order); return true; } diff --git a/TechTest/AnyCompany.Services/Services/IOrderService.cs b/TechTest/AnyCompany.Services/Services/IOrderService.cs index 86c3214..227c155 100644 --- a/TechTest/AnyCompany.Services/Services/IOrderService.cs +++ b/TechTest/AnyCompany.Services/Services/IOrderService.cs @@ -2,7 +2,7 @@ namespace AnyCompany.Services.Services { - interface IOrderService + public interface IOrderService { bool PlaceOrder(OrderDto order, int customerId); } diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index 487c8e9..d25e8c4 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -59,12 +59,26 @@ - + + + + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + AnyCompany.Data.Contract + + + {695D4D06-2336-4B91-BA68-4A4924CF4729} + AnyCompany.Models + + + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} + AnyCompany.Services + + diff --git a/TechTest/AnyCompany.Tests/Class1.cs b/TechTest/AnyCompany.Tests/Class1.cs deleted file mode 100644 index 5957505..0000000 --- a/TechTest/AnyCompany.Tests/Class1.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AnyCompany.Tests -{ - public class Class1 - { - } -} diff --git a/TechTest/AnyCompany.Tests/OrderServiceTests.cs b/TechTest/AnyCompany.Tests/OrderServiceTests.cs new file mode 100644 index 0000000..1e02ae6 --- /dev/null +++ b/TechTest/AnyCompany.Tests/OrderServiceTests.cs @@ -0,0 +1,77 @@ +using System; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Models; +using AnyCompany.Services; +using AnyCompany.Services.Dtos; +using Moq; +using NUnit.Framework; + +namespace AnyCompany.Tests +{ + [TestFixture] + public class OrderServiceTests + { + private Mock _orderRepositoryMock; + private Mock _customerRepositoryMock; + + [SetUp] + public void Setup() + { + _orderRepositoryMock = new Mock(); + _customerRepositoryMock = new Mock(); + } + + [TestCase("UK", 0.2)] + [TestCase("FR", 0)] + public void PlaceOrder_ShouldLoadACustomerAndPlaceAnOrder_UK(string country, double vat) + { + // Arrange. + const int CustomerId = 23; + var orderDto = GetOrderDto(); + + var customer = GetCustomer(country); + _customerRepositoryMock.Setup(r => r.Load(CustomerId)) + .Returns(customer); + + Order inOrder = null; + _orderRepositoryMock.Setup(o => o.Add(It.IsAny())) + .Callback(o => inOrder = o); + + var orderService = GetOrderService(); + + // Act. + var result = orderService.PlaceOrder(orderDto, CustomerId); + + // Assert. + Assert.IsTrue(result); + Assert.AreEqual(orderDto.Amount, inOrder.Amount); + Assert.AreEqual(vat, inOrder.VAT); + Assert.AreEqual(CustomerId, inOrder.CustomerId); + } + + private Customer GetCustomer(string country) + { + return new Customer + { + Name = "John Smith", + DateOfBirth = DateTime.UtcNow.AddYears(-31), + Country = country + }; + } + + private OrderService GetOrderService() + { + return new OrderService( + _orderRepositoryMock.Object, + _customerRepositoryMock.Object); + } + + private OrderDto GetOrderDto() + { + return new OrderDto + { + Amount = 20, + }; + } + } +} diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index da160a3..82e0fe8 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -30,6 +30,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Models", "AnyCom EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Services", "AnyCompany.Services\AnyCompany.Services.csproj", "{C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Data.Contract", "AnyCompany.Data.Contract\AnyCompany.Data.Contract.csproj", "{7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Data.Dapper", "AnyCompany.Data.Dapper\AnyCompany.Data.Dapper.csproj", "{CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +56,14 @@ Global {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D}.Release|Any CPU.Build.0 = Release|Any CPU + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7}.Release|Any CPU.Build.0 = Release|Any CPU + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -61,6 +73,8 @@ Global {DF80D016-3AFE-43B3-BC8F-3CEC313B4C91} = {118DE10D-B767-428F-B9D3-3987DF71146A} {695D4D06-2336-4B91-BA68-4A4924CF4729} = {DB29E18C-EE03-4193-857E-FD98D35D783A} {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} = {A38327A3-2391-4469-AB71-3AB3714531BB} + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} = {A38327A3-2391-4469-AB71-3AB3714531BB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D629674-23F9-49DA-971E-77A5D7A7C39D} From 9cd227beb8764d752070c3976133fd1f31cc689e Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:22:17 +0100 Subject: [PATCH 05/13] Refactored VAT calculation into a static helper --- .../AnyCompany.Services/AnyCompany.Services.csproj | 2 ++ .../AnyCompany.Services/Constants/VatConstants.cs | 8 ++++++++ .../AnyCompany.Services/Helpers/VatCalculator.cs | 12 ++++++++++++ TechTest/AnyCompany.Services/OrderService.cs | 6 ++---- 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 TechTest/AnyCompany.Services/Constants/VatConstants.cs create mode 100644 TechTest/AnyCompany.Services/Helpers/VatCalculator.cs diff --git a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj index 45efd41..2301b0a 100644 --- a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -40,7 +40,9 @@ + + diff --git a/TechTest/AnyCompany.Services/Constants/VatConstants.cs b/TechTest/AnyCompany.Services/Constants/VatConstants.cs new file mode 100644 index 0000000..ecca404 --- /dev/null +++ b/TechTest/AnyCompany.Services/Constants/VatConstants.cs @@ -0,0 +1,8 @@ +namespace AnyCompany.Services.Constants +{ + public static class VatConstants + { + public const double UkVat = 0.2d; + public const double RowVat = 0; + } +} diff --git a/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs b/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs new file mode 100644 index 0000000..dbdd92b --- /dev/null +++ b/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs @@ -0,0 +1,12 @@ +using AnyCompany.Services.Constants; + +namespace AnyCompany.Services.Helpers +{ + internal static class VatCalculator + { + internal static double GetVat(string country) + { + return country == "UK" ? VatConstants.UkVat : VatConstants.RowVat; + } + } +} diff --git a/TechTest/AnyCompany.Services/OrderService.cs b/TechTest/AnyCompany.Services/OrderService.cs index f41739f..a2094a3 100644 --- a/TechTest/AnyCompany.Services/OrderService.cs +++ b/TechTest/AnyCompany.Services/OrderService.cs @@ -1,6 +1,7 @@ using AnyCompany.Data.Contract.Repositories; using AnyCompany.Models; using AnyCompany.Services.Dtos; +using AnyCompany.Services.Helpers; using AnyCompany.Services.Mappers; using AnyCompany.Services.Services; @@ -24,10 +25,7 @@ public bool PlaceOrder(OrderDto orderDto, int customerId) if (orderDto.Amount == 0) return false; - if (customer.Country == "UK") - orderDto.VAT = 0.2d; - else - orderDto.VAT = 0; + orderDto.VAT = VatCalculator.GetVat(customer.Country); var order = OrderMapper.Map(orderDto); order.CustomerId = customerId; From c090d44c71c1b24ae1a481fb22b84d7855174eb8 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:27:56 +0100 Subject: [PATCH 06/13] Added tests around the OrderService --- .../AnyCompany.Services.csproj | 1 + .../Exceptions/CustomerNotFoundException.cs | 15 ++++++++ TechTest/AnyCompany.Services/OrderService.cs | 7 ++++ .../AnyCompany.Tests/OrderServiceTests.cs | 38 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 TechTest/AnyCompany.Services/Exceptions/CustomerNotFoundException.cs diff --git a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj index 2301b0a..295ef2a 100644 --- a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -42,6 +42,7 @@ + diff --git a/TechTest/AnyCompany.Services/Exceptions/CustomerNotFoundException.cs b/TechTest/AnyCompany.Services/Exceptions/CustomerNotFoundException.cs new file mode 100644 index 0000000..6125841 --- /dev/null +++ b/TechTest/AnyCompany.Services/Exceptions/CustomerNotFoundException.cs @@ -0,0 +1,15 @@ +using System; + +namespace AnyCompany.Services.Exceptions +{ + public class CustomerNotFoundException : Exception + { + public CustomerNotFoundException() + { + } + + public CustomerNotFoundException(string message) : base(message) + { + } + } +} diff --git a/TechTest/AnyCompany.Services/OrderService.cs b/TechTest/AnyCompany.Services/OrderService.cs index a2094a3..3f9bad1 100644 --- a/TechTest/AnyCompany.Services/OrderService.cs +++ b/TechTest/AnyCompany.Services/OrderService.cs @@ -1,6 +1,7 @@ using AnyCompany.Data.Contract.Repositories; using AnyCompany.Models; using AnyCompany.Services.Dtos; +using AnyCompany.Services.Exceptions; using AnyCompany.Services.Helpers; using AnyCompany.Services.Mappers; using AnyCompany.Services.Services; @@ -20,13 +21,19 @@ public OrderService(IOrderRepository orderRepository, ICustomerRepository custom public bool PlaceOrder(OrderDto orderDto, int customerId) { + // load the customer Customer customer = _customerRepository.Load(customerId); + if (customer == null) + throw new CustomerNotFoundException(); + // do not proceed with order if amount is 0 if (orderDto.Amount == 0) return false; + // get the appropriate vat for the customers country orderDto.VAT = VatCalculator.GetVat(customer.Country); + // build and save the order var order = OrderMapper.Map(orderDto); order.CustomerId = customerId; diff --git a/TechTest/AnyCompany.Tests/OrderServiceTests.cs b/TechTest/AnyCompany.Tests/OrderServiceTests.cs index 1e02ae6..c6df476 100644 --- a/TechTest/AnyCompany.Tests/OrderServiceTests.cs +++ b/TechTest/AnyCompany.Tests/OrderServiceTests.cs @@ -3,6 +3,7 @@ using AnyCompany.Models; using AnyCompany.Services; using AnyCompany.Services.Dtos; +using AnyCompany.Services.Exceptions; using Moq; using NUnit.Framework; @@ -49,6 +50,43 @@ public void PlaceOrder_ShouldLoadACustomerAndPlaceAnOrder_UK(string country, dou Assert.AreEqual(CustomerId, inOrder.CustomerId); } + [Test] + public void PlaceOrder_CustomerNotFound_ShouldThrowException() + { + // Arrange. + const int CustomerId = 44; + + _customerRepositoryMock.Setup(r => r.Load(CustomerId)) + .Returns((Customer)null); + + var orderService = GetOrderService(); + + // Act/Assert. + Assert.Throws(() => orderService.PlaceOrder(GetOrderDto(), CustomerId)); + } + + [Test] + public void PlaceOrder_AmountIs0_ShouldReturnFalse() + { + // Arrange. + const int CustomerId = 23; + var orderDto = GetOrderDto(); + + var customer = GetCustomer("UK"); + _customerRepositoryMock.Setup(r => r.Load(CustomerId)) + .Returns(customer); + + orderDto.Amount = 0; + + var orderService = GetOrderService(); + + // Act. + var result = orderService.PlaceOrder(orderDto, CustomerId); + + // Assert. + Assert.IsFalse(result); + } + private Customer GetCustomer(string country) { return new Customer From 97940ba2b5b0d02c92e340f51390ccb7c9ee2d43 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:44:13 +0100 Subject: [PATCH 07/13] Refactored OrderRepository --- TechTest/AddOrders.sql | 10 ++++++ .../AnyCompany.Data.Dapper.csproj | 9 +++++ .../Enums/ConnectionType.cs | 8 +++++ .../Factories/ConnectionFactory.cs | 27 ++++++++++++++ .../Factories/IConnectionFactory.cs | 10 ++++++ .../Repositories/OrderRepository.cs | 36 ++++++++++--------- .../AnyCompany.Data.Dapper/packages.config | 4 +++ .../Bootstrap/IntegrationTestInstaller.cs | 15 +++++++- 8 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 TechTest/AnyCompany.Data.Dapper/Enums/ConnectionType.cs create mode 100644 TechTest/AnyCompany.Data.Dapper/Factories/ConnectionFactory.cs create mode 100644 TechTest/AnyCompany.Data.Dapper/Factories/IConnectionFactory.cs create mode 100644 TechTest/AnyCompany.Data.Dapper/packages.config diff --git a/TechTest/AddOrders.sql b/TechTest/AddOrders.sql index 24a0a92..0863e55 100644 --- a/TechTest/AddOrders.sql +++ b/TechTest/AddOrders.sql @@ -1,6 +1,7 @@ USE [Orders] GO +-- base table IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' @@ -13,3 +14,12 @@ BEGIN [VAT] DECIMAL NOT NULL ) END + +-- first migration +IF NOT EXISTS(SELECT 1 FROM sys.columns + WHERE Name = N'CustomerId' + AND Object_ID = Object_ID(N'dbo.Orders')) +BEGIN + ALTER TABLE dbo.Orders + ADD CustomerId INT NOT NULL DEFAULT 0; +END \ No newline at end of file diff --git a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj index aa5b48d..f1f7f9a 100644 --- a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj +++ b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj @@ -31,6 +31,9 @@ 4 + + ..\packages\Dapper.1.60.6\lib\net451\Dapper.dll + @@ -41,6 +44,9 @@ + + + @@ -56,5 +62,8 @@ AnyCompany.Models + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Data.Dapper/Enums/ConnectionType.cs b/TechTest/AnyCompany.Data.Dapper/Enums/ConnectionType.cs new file mode 100644 index 0000000..339f0fb --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Enums/ConnectionType.cs @@ -0,0 +1,8 @@ +namespace AnyCompany.Data.Dapper.Enums +{ + public enum ConnectionType + { + CustomerDb = 0, + OrderDb = 1 + } +} diff --git a/TechTest/AnyCompany.Data.Dapper/Factories/ConnectionFactory.cs b/TechTest/AnyCompany.Data.Dapper/Factories/ConnectionFactory.cs new file mode 100644 index 0000000..b03d61d --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Factories/ConnectionFactory.cs @@ -0,0 +1,27 @@ +using System.Data; +using System.Data.SqlClient; +using AnyCompany.Data.Dapper.Enums; + +namespace AnyCompany.Data.Dapper.Factories +{ + public class ConnectionFactory : IConnectionFactory + { + private readonly string _customerConnectionString; + private readonly string _orderDbConnectionString; + + public ConnectionFactory(string customerConnectionString, string orderDbConnectionString) + { + _customerConnectionString = customerConnectionString; + _orderDbConnectionString = orderDbConnectionString; + } + + public IDbConnection Create(ConnectionType connectionType) + { + var connectionString = connectionType == ConnectionType.CustomerDb + ? _customerConnectionString + : _orderDbConnectionString; + + return new SqlConnection(connectionString); + } + } +} diff --git a/TechTest/AnyCompany.Data.Dapper/Factories/IConnectionFactory.cs b/TechTest/AnyCompany.Data.Dapper/Factories/IConnectionFactory.cs new file mode 100644 index 0000000..ee5e551 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Factories/IConnectionFactory.cs @@ -0,0 +1,10 @@ +using System.Data; +using AnyCompany.Data.Dapper.Enums; + +namespace AnyCompany.Data.Dapper.Factories +{ + public interface IConnectionFactory + { + IDbConnection Create(ConnectionType connectionType); + } +} diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs index 32d55ee..d1f3b1d 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs @@ -1,32 +1,34 @@ -using System.Data.SqlClient; -using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Data.Dapper.Enums; +using AnyCompany.Data.Dapper.Factories; using AnyCompany.Models; +using Dapper; namespace AnyCompany.Data.Dapper.Repositories { public class OrderRepository : IOrderRepository { - private static string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; + private readonly IConnectionFactory _connectionFactory; - public void Save(Order order) + public OrderRepository(IConnectionFactory connectionFactory) { - SqlConnection connection = new SqlConnection(ConnectionString); - connection.Open(); - - SqlCommand command = new SqlCommand("INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT)", connection); - - command.Parameters.AddWithValue("@OrderId", order.OrderId); - command.Parameters.AddWithValue("@Amount", order.Amount); - command.Parameters.AddWithValue("@VAT", order.VAT); - - command.ExecuteNonQuery(); - - connection.Close(); + _connectionFactory = connectionFactory; } public void Add(Order order) { - Save(order); // TODO just a proxy for now + using (var connection = _connectionFactory.Create(ConnectionType.OrderDb)) + { + connection.Open(); + connection.Execute(@"INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId)", + new + { + OrderId = order.OrderId, + Amount = order.Amount, + VAT = order.VAT, + CustomerId = order.CustomerId + }); + } } } } diff --git a/TechTest/AnyCompany.Data.Dapper/packages.config b/TechTest/AnyCompany.Data.Dapper/packages.config new file mode 100644 index 0000000..b6f2e13 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs b/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs index cceed97..5b58de4 100644 --- a/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs +++ b/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs @@ -1,7 +1,10 @@ -using AnyCompany.Data.Contract.Repositories; +using System.Configuration; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Data.Dapper.Factories; using AnyCompany.Data.Dapper.Repositories; using AnyCompany.Services; using AnyCompany.Services.Services; +using Castle.MicroKernel; using Castle.MicroKernel.Registration; using Castle.MicroKernel.SubSystems.Configuration; using Castle.Windsor; @@ -20,6 +23,16 @@ public void Install(IWindsorContainer container, IConfigurationStore store) container.Register( Component.For()); + + container.Register( + Component.For().UsingFactoryMethod(CreateConnectionFactory)); + } + + private IConnectionFactory CreateConnectionFactory(IKernel kernel) + { + return new ConnectionFactory( + ConfigurationManager.ConnectionStrings["CustomerConnectionString"].ConnectionString, + ConfigurationManager.ConnectionStrings["OrderConnectionString"].ConnectionString); } } } From d75a8a0a0782abf83e775c5278bce9ee0b1b59e8 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:48:56 +0100 Subject: [PATCH 08/13] Moved OrderService into Services folder --- TechTest/AnyCompany.Services/AnyCompany.Services.csproj | 2 +- TechTest/AnyCompany.Services/{ => Services}/OrderService.cs | 3 +-- TechTest/AnyCompany.Tests/OrderServiceTests.cs | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) rename TechTest/AnyCompany.Services/{ => Services}/OrderService.cs (95%) diff --git a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj index 295ef2a..04784de 100644 --- a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -45,7 +45,7 @@ - + diff --git a/TechTest/AnyCompany.Services/OrderService.cs b/TechTest/AnyCompany.Services/Services/OrderService.cs similarity index 95% rename from TechTest/AnyCompany.Services/OrderService.cs rename to TechTest/AnyCompany.Services/Services/OrderService.cs index 3f9bad1..aef7449 100644 --- a/TechTest/AnyCompany.Services/OrderService.cs +++ b/TechTest/AnyCompany.Services/Services/OrderService.cs @@ -4,9 +4,8 @@ using AnyCompany.Services.Exceptions; using AnyCompany.Services.Helpers; using AnyCompany.Services.Mappers; -using AnyCompany.Services.Services; -namespace AnyCompany.Services +namespace AnyCompany.Services.Services { public class OrderService : IOrderService { diff --git a/TechTest/AnyCompany.Tests/OrderServiceTests.cs b/TechTest/AnyCompany.Tests/OrderServiceTests.cs index c6df476..6fc61e5 100644 --- a/TechTest/AnyCompany.Tests/OrderServiceTests.cs +++ b/TechTest/AnyCompany.Tests/OrderServiceTests.cs @@ -4,6 +4,7 @@ using AnyCompany.Services; using AnyCompany.Services.Dtos; using AnyCompany.Services.Exceptions; +using AnyCompany.Services.Services; using Moq; using NUnit.Framework; From 2a02d902e5f21feb5a8013e8405883ef5a98588b Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 9 Aug 2019 23:53:33 +0100 Subject: [PATCH 09/13] Moved IoC into separate project --- .../AnyCompany.IntegrationTests.csproj | 6 +- .../OrderServiceIntegrationTests.cs | 2 +- TechTest/AnyCompany.Ioc/AnyCompany.Ioc.csproj | 75 +++++++++++++++++++ .../Bootstrapper.cs | 8 +- .../DependencyInstaller.cs} | 5 +- .../AnyCompany.Ioc/Properties/AssemblyInfo.cs | 36 +++++++++ TechTest/AnyCompany.Ioc/packages.config | 5 ++ TechTest/TechTest.sln | 7 ++ 8 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 TechTest/AnyCompany.Ioc/AnyCompany.Ioc.csproj rename TechTest/{AnyCompany.IntegrationTests/Bootstrap => AnyCompany.Ioc}/Bootstrapper.cs (70%) rename TechTest/{AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs => AnyCompany.Ioc/DependencyInstaller.cs} (90%) create mode 100644 TechTest/AnyCompany.Ioc/Properties/AssemblyInfo.cs create mode 100644 TechTest/AnyCompany.Ioc/packages.config diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj index aac9af8..9d6cd5f 100644 --- a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -66,8 +66,6 @@ - - @@ -86,6 +84,10 @@ {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} AnyCompany.Data.Dapper + + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA} + AnyCompany.Ioc + {695D4D06-2336-4B91-BA68-4A4924CF4729} AnyCompany.Models diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs index a5573f6..45b4d42 100644 --- a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -1,6 +1,6 @@ using System; -using AnyCompany.IntegrationTests.Bootstrap; using AnyCompany.IntegrationTests.DataHelpers; +using AnyCompany.Ioc; using AnyCompany.Models; using AnyCompany.Services.Dtos; using AnyCompany.Services.Services; diff --git a/TechTest/AnyCompany.Ioc/AnyCompany.Ioc.csproj b/TechTest/AnyCompany.Ioc/AnyCompany.Ioc.csproj new file mode 100644 index 0000000..3808873 --- /dev/null +++ b/TechTest/AnyCompany.Ioc/AnyCompany.Ioc.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA} + Library + Properties + AnyCompany.Ioc + AnyCompany.Ioc + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\Castle.Windsor.5.0.0\lib\net45\Castle.Windsor.dll + + + + + + + + + + + + + + + + + + + + + + + + {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} + AnyCompany.Data.Contract + + + {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} + AnyCompany.Data.Dapper + + + {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} + AnyCompany.Services + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs b/TechTest/AnyCompany.Ioc/Bootstrapper.cs similarity index 70% rename from TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs rename to TechTest/AnyCompany.Ioc/Bootstrapper.cs index 6c2979b..4e1f915 100644 --- a/TechTest/AnyCompany.IntegrationTests/Bootstrap/Bootstrapper.cs +++ b/TechTest/AnyCompany.Ioc/Bootstrapper.cs @@ -1,14 +1,14 @@ using Castle.Windsor; -namespace AnyCompany.IntegrationTests.Bootstrap +namespace AnyCompany.Ioc { - internal static class Bootstrapper + public static class Bootstrapper { private static object _objLock = new object(); private static WindsorContainer _container; - internal static WindsorContainer GetContainer() + public static WindsorContainer GetContainer() { if (_container != null) return _container; @@ -19,7 +19,7 @@ internal static WindsorContainer GetContainer() return _container; var container = new WindsorContainer(); - container.Install(new IntegrationTestInstaller()); + container.Install(new DependencyInstaller()); _container = container; return _container; diff --git a/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs b/TechTest/AnyCompany.Ioc/DependencyInstaller.cs similarity index 90% rename from TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs rename to TechTest/AnyCompany.Ioc/DependencyInstaller.cs index 5b58de4..4484969 100644 --- a/TechTest/AnyCompany.IntegrationTests/Bootstrap/IntegrationTestInstaller.cs +++ b/TechTest/AnyCompany.Ioc/DependencyInstaller.cs @@ -2,16 +2,15 @@ using AnyCompany.Data.Contract.Repositories; using AnyCompany.Data.Dapper.Factories; using AnyCompany.Data.Dapper.Repositories; -using AnyCompany.Services; using AnyCompany.Services.Services; using Castle.MicroKernel; using Castle.MicroKernel.Registration; using Castle.MicroKernel.SubSystems.Configuration; using Castle.Windsor; -namespace AnyCompany.IntegrationTests.Bootstrap +namespace AnyCompany.Ioc { - public class IntegrationTestInstaller : IWindsorInstaller + public class DependencyInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { diff --git a/TechTest/AnyCompany.Ioc/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Ioc/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..133d763 --- /dev/null +++ b/TechTest/AnyCompany.Ioc/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyCompany.Ioc")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.Ioc")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("be9d1186-d67f-4ba0-9a94-ce529cc1fefa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.Ioc/packages.config b/TechTest/AnyCompany.Ioc/packages.config new file mode 100644 index 0000000..927252c --- /dev/null +++ b/TechTest/AnyCompany.Ioc/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 82e0fe8..ba64203 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Data.Contract", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Data.Dapper", "AnyCompany.Data.Dapper\AnyCompany.Data.Dapper.csproj", "{CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Ioc", "AnyCompany.Ioc\AnyCompany.Ioc.csproj", "{BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,10 @@ Global {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE}.Release|Any CPU.Build.0 = Release|Any CPU + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,6 +81,7 @@ Global {C7E15594-7D8F-4C18-9DD7-14F3FBB1572D} = {25F15798-EF39-43DD-B9A8-11DA2D8EB5EB} {7F4D6121-26EF-48A2-A2F4-BBD5D114D8E7} = {A38327A3-2391-4469-AB71-3AB3714531BB} {CEF90CEA-CEDF-44B4-9203-9B9ADF9A15CE} = {A38327A3-2391-4469-AB71-3AB3714531BB} + {BE9D1186-D67F-4BA0-9A94-CE529CC1FEFA} = {DB29E18C-EE03-4193-857E-FD98D35D783A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D629674-23F9-49DA-971E-77A5D7A7C39D} From 9ba52e00d3d88fb5734a1a394495f90ad5eb9c73 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sat, 10 Aug 2019 00:00:23 +0100 Subject: [PATCH 10/13] Refactored CustomerRepository --- .../AnyCompany.Data.Dapper.csproj | 1 + .../Repositories/CustomerRepository.cs | 28 +++++------------- TechTest/AnyCompany.Ioc/Container.cs | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 TechTest/AnyCompany.Ioc/Container.cs diff --git a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj index f1f7f9a..7dc1e8f 100644 --- a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj +++ b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj @@ -35,6 +35,7 @@ ..\packages\Dapper.1.60.6\lib\net451\Dapper.dll + diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs index 064c3eb..8ed1f6e 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs @@ -1,34 +1,22 @@ -using System; +using System.Configuration; using System.Data.SqlClient; +using System.Linq; using AnyCompany.Models; +using Dapper; namespace AnyCompany.Data.Dapper.Repositories { public static class CustomerRepository { - private static string ConnectionString = @"Data Source=(local);Database=Customers;User Id=admin;Password=password;"; - public static Customer Load(int customerId) { - Customer customer = new Customer(); - - SqlConnection connection = new SqlConnection(ConnectionString); - connection.Open(); - - SqlCommand command = new SqlCommand("SELECT * FROM Customer WHERE CustomerId = " + customerId, - connection); - var reader = command.ExecuteReader(); - - while (reader.Read()) + var connectionString = ConfigurationManager.ConnectionStrings["CustomerConnectionString"].ConnectionString; + using (var connection = new SqlConnection(connectionString)) { - customer.Name = reader["Name"].ToString(); - customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); - customer.Country = reader["Country"].ToString(); + connection.Open(); + return connection.Query("SELECT * FROM Customer WHERE CustomerId = @CustomerId", + new {CustomerId = customerId}).FirstOrDefault(); } - - connection.Close(); - - return customer; } } } diff --git a/TechTest/AnyCompany.Ioc/Container.cs b/TechTest/AnyCompany.Ioc/Container.cs new file mode 100644 index 0000000..251ebc7 --- /dev/null +++ b/TechTest/AnyCompany.Ioc/Container.cs @@ -0,0 +1,29 @@ +using Castle.Windsor; + +namespace AnyCompany.Ioc +{ + public static class Container + { + private static object _objLock = new object(); + + private static WindsorContainer _container; + + public static WindsorContainer GetContainer() + { + if (_container != null) + return _container; + + lock (_objLock) + { + if (_container != null) + return _container; + + var container = new WindsorContainer(); + container.Install(new DependencyInstaller()); + + _container = container; + return _container; + } + } + } +} From 31e147feed914d2fc61473cb580f8b043f6f5b7e Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sat, 10 Aug 2019 00:26:52 +0100 Subject: [PATCH 11/13] Added Customer Service --- .../Repositories/ICustomerRepository.cs | 4 +- .../Repositories/IOrderRepository.cs | 3 +- .../Repositories/CustomerRepositoryWrapper.cs | 8 +- .../Repositories/OrderRepository.cs | 8 +- .../AnyCompany.Services.csproj | 5 + .../AnyCompany.Services/Dtos/CustomerDto.cs | 15 +++ .../Dtos/CustomerOrdersDto.cs | 12 +++ .../Mappers/CustomerMapper.cs | 19 ++++ .../Mappers/OrderMapper.cs | 10 ++ .../Services/CustomerOrderService.cs | 42 ++++++++ .../Services/ICustomerOrderService.cs | 10 ++ .../AnyCompany.Tests/AnyCompany.Tests.csproj | 1 + .../CustomerOrderServiceTests.cs | 101 ++++++++++++++++++ 13 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 TechTest/AnyCompany.Services/Dtos/CustomerDto.cs create mode 100644 TechTest/AnyCompany.Services/Dtos/CustomerOrdersDto.cs create mode 100644 TechTest/AnyCompany.Services/Mappers/CustomerMapper.cs create mode 100644 TechTest/AnyCompany.Services/Services/CustomerOrderService.cs create mode 100644 TechTest/AnyCompany.Services/Services/ICustomerOrderService.cs create mode 100644 TechTest/AnyCompany.Tests/CustomerOrderServiceTests.cs diff --git a/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs b/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs index f0c2b3f..433c46f 100644 --- a/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs +++ b/TechTest/AnyCompany.Data.Contract/Repositories/ICustomerRepository.cs @@ -1,9 +1,11 @@ -using AnyCompany.Models; +using System.Collections.Generic; +using AnyCompany.Models; namespace AnyCompany.Data.Contract.Repositories { public interface ICustomerRepository { Customer Load(int customerId); + IEnumerable GetList(); } } diff --git a/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs b/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs index c14763e..6bcb6a5 100644 --- a/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs +++ b/TechTest/AnyCompany.Data.Contract/Repositories/IOrderRepository.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Collections.Generic; using AnyCompany.Models; namespace AnyCompany.Data.Contract.Repositories @@ -6,5 +6,6 @@ namespace AnyCompany.Data.Contract.Repositories public interface IOrderRepository { void Add(Order order); + IEnumerable GetList(); } } diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs index 7a03ee8..c64ef26 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs @@ -1,4 +1,5 @@ -using AnyCompany.Data.Contract.Repositories; +using System.Collections.Generic; +using AnyCompany.Data.Contract.Repositories; using AnyCompany.Models; namespace AnyCompany.Data.Dapper.Repositories @@ -11,5 +12,10 @@ public Customer Load(int customerId) { return CustomerRepository.Load(customerId); } + + public IEnumerable GetList() + { + throw new System.NotImplementedException(); + } } } diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs index d1f3b1d..8eb53a4 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs @@ -1,4 +1,5 @@ -using AnyCompany.Data.Contract.Repositories; +using System.Collections.Generic; +using AnyCompany.Data.Contract.Repositories; using AnyCompany.Data.Dapper.Enums; using AnyCompany.Data.Dapper.Factories; using AnyCompany.Models; @@ -30,5 +31,10 @@ public void Add(Order order) }); } } + + public IEnumerable GetList() + { + throw new System.NotImplementedException(); + } } } diff --git a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj index 04784de..8186b56 100644 --- a/TechTest/AnyCompany.Services/AnyCompany.Services.csproj +++ b/TechTest/AnyCompany.Services/AnyCompany.Services.csproj @@ -41,10 +41,15 @@ + + + + + diff --git a/TechTest/AnyCompany.Services/Dtos/CustomerDto.cs b/TechTest/AnyCompany.Services/Dtos/CustomerDto.cs new file mode 100644 index 0000000..b75328b --- /dev/null +++ b/TechTest/AnyCompany.Services/Dtos/CustomerDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace AnyCompany.Services.Dtos +{ + public class CustomerDto + { + public int CustomerId { get; set; } + + public string Country { get; set; } + + public DateTime DateOfBirth { get; set; } + + public string Name { get; set; } + } +} diff --git a/TechTest/AnyCompany.Services/Dtos/CustomerOrdersDto.cs b/TechTest/AnyCompany.Services/Dtos/CustomerOrdersDto.cs new file mode 100644 index 0000000..5ff7144 --- /dev/null +++ b/TechTest/AnyCompany.Services/Dtos/CustomerOrdersDto.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using AnyCompany.Models; + +namespace AnyCompany.Services.Dtos +{ + public class CustomerOrdersDto + { + public CustomerDto Customer { get; set; } + + public IEnumerable Orders { get; set; } + } +} diff --git a/TechTest/AnyCompany.Services/Mappers/CustomerMapper.cs b/TechTest/AnyCompany.Services/Mappers/CustomerMapper.cs new file mode 100644 index 0000000..969c669 --- /dev/null +++ b/TechTest/AnyCompany.Services/Mappers/CustomerMapper.cs @@ -0,0 +1,19 @@ +using AnyCompany.Models; +using AnyCompany.Services.Dtos; + +namespace AnyCompany.Services.Mappers +{ + public static class CustomerMapper + { + public static CustomerDto Map(Customer customer) + { + return new CustomerDto + { + CustomerId = customer.CustomerId, + Country = customer.Country, + DateOfBirth = customer.DateOfBirth, + Name = customer.Name + }; + } + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs b/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs index 07030ba..5812f1f 100644 --- a/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs +++ b/TechTest/AnyCompany.Services/Mappers/OrderMapper.cs @@ -14,5 +14,15 @@ public static Order Map(OrderDto orderDto) VAT = orderDto.VAT }; } + + public static OrderDto Map(Order order) + { + return new OrderDto + { + OrderId = order.OrderId, + Amount = order.Amount, + VAT = order.VAT + }; + } } } diff --git a/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs b/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs new file mode 100644 index 0000000..9a0dd87 --- /dev/null +++ b/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Services.Dtos; +using AnyCompany.Services.Mappers; + +namespace AnyCompany.Services.Services +{ + public class CustomerOrderService : ICustomerOrderService + { + private readonly ICustomerRepository _customerRepository; + private readonly IOrderRepository _orderRepository; + + public CustomerOrderService(ICustomerRepository customerRepository, IOrderRepository orderRepository) + { + _customerRepository = customerRepository; + _orderRepository = orderRepository; + } + + public IEnumerable GetAllCustomerWithOrders() + { + var customerOrderDtos = new List(); + + // load data + var customers = _customerRepository.GetList(); + var orders = _orderRepository.GetList(); + + foreach (var customer in customers) + { + var customerOrderDto = new CustomerOrdersDto + { + Customer = CustomerMapper.Map(customer), + Orders = orders.Where(o => o.CustomerId == customer.CustomerId).Select(OrderMapper.Map) + }; + + customerOrderDtos.Add(customerOrderDto); + } + + return customerOrderDtos; + } + } +} diff --git a/TechTest/AnyCompany.Services/Services/ICustomerOrderService.cs b/TechTest/AnyCompany.Services/Services/ICustomerOrderService.cs new file mode 100644 index 0000000..8fa937d --- /dev/null +++ b/TechTest/AnyCompany.Services/Services/ICustomerOrderService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using AnyCompany.Services.Dtos; + +namespace AnyCompany.Services.Services +{ + public interface ICustomerOrderService + { + IEnumerable GetAllCustomerWithOrders(); + } +} diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index d25e8c4..e9b4334 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -59,6 +59,7 @@ + diff --git a/TechTest/AnyCompany.Tests/CustomerOrderServiceTests.cs b/TechTest/AnyCompany.Tests/CustomerOrderServiceTests.cs new file mode 100644 index 0000000..661eccd --- /dev/null +++ b/TechTest/AnyCompany.Tests/CustomerOrderServiceTests.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Models; +using AnyCompany.Services.Services; +using Moq; +using NUnit.Framework; + +namespace AnyCompany.Tests +{ + [TestFixture] + public class CustomerOrderServiceTests + { + private Mock _customerRepositoryMock; + private Mock _orderRepositoryMock; + + [SetUp] + public void Setup() + { + _orderRepositoryMock = new Mock(); + _customerRepositoryMock = new Mock(); + } + + [Test] + public void GetCustomersWithOrders_ShouldReturnAllOrders() + { + // Arrange. + const int CustomerOneId = 3; + const int CustomerTwoId = 4; + + var customers = GetCustomers(CustomerOneId, CustomerTwoId); + _customerRepositoryMock.Setup(r => r.GetList()).Returns(customers); + + var orders = GetOrders(CustomerOneId, CustomerTwoId); + _orderRepositoryMock.Setup(r => r.GetList()).Returns(orders); + + var customerOrderService = GetService(); + + // Act. + var result = customerOrderService.GetAllCustomerWithOrders(); + + // Assert. + Assert.AreEqual(customers.Count(), result.Count()); + Assert.AreEqual(customers.First().CustomerId, result.First().Customer.CustomerId); + Assert.AreEqual(customers.First().Country, result.First().Customer.Country); + Assert.AreEqual(customers.First().DateOfBirth, result.First().Customer.DateOfBirth); + Assert.AreEqual(customers.First().Name, result.First().Customer.Name); + Assert.AreEqual(orders.Count(o => o.CustomerId == customers.First().CustomerId), result.First().Orders.Count()); + Assert.AreEqual(orders.First(o => o.CustomerId == customers.First().CustomerId).Amount, result.First().Orders.First().Amount); + Assert.AreEqual(orders.First(o => o.CustomerId == customers.First().CustomerId).VAT, result.First().Orders.First().VAT); + Assert.AreEqual(orders.First(o => o.CustomerId == customers.First().CustomerId).OrderId, result.First().Orders.First().OrderId); + } + + private CustomerOrderService GetService() + { + return new CustomerOrderService( + _customerRepositoryMock.Object, + _orderRepositoryMock.Object); + } + + private IEnumerable GetCustomers(int idOne, int idTwo) + { + return new List + { + GetCustomer(idOne), + GetCustomer(idTwo) + }; + } + + private IEnumerable GetOrders(int idOne, int idTwo) + { + return new List + { + GetOrder(33, idOne), + GetOrder(43, idTwo), + GetOrder(43, idOne), + }; + } + + private Customer GetCustomer(int id) + { + return new Customer + { + CustomerId = id, + Name = "John Smith", + Country = "UK" + }; + } + + private Order GetOrder(int id, int customerId) + { + return new Order + { + OrderId = id, + Amount = 20.99, + CustomerId = customerId, + VAT = 0.2d + }; + } + } +} From f3569d11cc8b4805ee1feeed747dac54680c04ba Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sat, 10 Aug 2019 11:54:59 +0100 Subject: [PATCH 12/13] Added integration test around CustomerOrderService --- TechTest/AddOrders.sql | 4 +- .../Repositories/CustomerRepositoryWrapper.cs | 17 ++++- .../Repositories/OrderRepository.cs | 15 +++- .../AnyCompany.IntegrationTests.csproj | 1 + .../CustomerServiceIntegrationTests.cs | 71 +++++++++++++++++++ .../DataHelpers/OrderDataHelper.cs | 19 +++++ .../OrderServiceIntegrationTests.cs | 17 +++-- .../AnyCompany.Ioc/DependencyInstaller.cs | 3 + 8 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs diff --git a/TechTest/AddOrders.sql b/TechTest/AddOrders.sql index 0863e55..3c75b18 100644 --- a/TechTest/AddOrders.sql +++ b/TechTest/AddOrders.sql @@ -10,8 +10,8 @@ BEGIN CREATE TABLE [dbo].[Orders] ( [OrderId] INT NOT NULL PRIMARY KEY, - [Amount] DECIMAL NOT NULL, - [VAT] DECIMAL NOT NULL + [Amount] DECIMAL(10, 2) NOT NULL, + [VAT] DECIMAL(10, 2) NOT NULL ) END diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs index c64ef26..aba79c0 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs @@ -1,11 +1,22 @@ using System.Collections.Generic; +using System.Linq; using AnyCompany.Data.Contract.Repositories; +using AnyCompany.Data.Dapper.Enums; +using AnyCompany.Data.Dapper.Factories; using AnyCompany.Models; +using Dapper; namespace AnyCompany.Data.Dapper.Repositories { public class CustomerRepositoryWrapper : ICustomerRepository { + private IConnectionFactory _connectionFactory; + + public CustomerRepositoryWrapper(IConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + // using this to act as a proxy to the static CustomerRepository - I assumed this was legacy that // we're trying to abstract and eventually replace public Customer Load(int customerId) @@ -15,7 +26,11 @@ public Customer Load(int customerId) public IEnumerable GetList() { - throw new System.NotImplementedException(); + using (var connection = _connectionFactory.Create(ConnectionType.CustomerDb)) + { + connection.Open(); + return connection.Query("SELECT CustomerId, Country, DateOfBirth, Name from dbo.Customer").ToList(); + } } } } diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs index 8eb53a4..ff3ec0f 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Data; +using System.Linq; using AnyCompany.Data.Contract.Repositories; using AnyCompany.Data.Dapper.Enums; using AnyCompany.Data.Dapper.Factories; @@ -18,7 +20,7 @@ public OrderRepository(IConnectionFactory connectionFactory) public void Add(Order order) { - using (var connection = _connectionFactory.Create(ConnectionType.OrderDb)) + using (var connection = CreateConnection()) { connection.Open(); connection.Execute(@"INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId)", @@ -34,7 +36,16 @@ public void Add(Order order) public IEnumerable GetList() { - throw new System.NotImplementedException(); + using (var connection = CreateConnection()) + { + connection.Open(); + return connection.Query("SELECT OrderId, Amount, VAT, CustomerId from dbo.Orders").ToList(); + } + } + + private IDbConnection CreateConnection() + { + return _connectionFactory.Create(ConnectionType.OrderDb); } } } diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj index 9d6cd5f..5670f60 100644 --- a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -66,6 +66,7 @@ + diff --git a/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs new file mode 100644 index 0000000..8206a2c --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs @@ -0,0 +1,71 @@ + +using System; +using System.Linq; +using AnyCompany.IntegrationTests.DataHelpers; +using AnyCompany.Ioc; +using AnyCompany.Models; +using AnyCompany.Services.Services; +using Castle.Windsor; +using NUnit.Framework; + +namespace AnyCompany.IntegrationTests +{ + [TestFixture] + public class CustomerServiceIntegrationTests + { + private IWindsorContainer _container; + + [SetUp] + public void SetupFixture() + { + _container = Bootstrapper.GetContainer(); + } + + [Test] + public void GetAllCustomersWithOrders_ShouldReturnCustomersAndOrders() + { + // Arrange. + var customerOne = CustomerDataHelper.Add(GetCustomer("UK")); + var orderOne = OrderDataHelper.AddOrder(GetOrder(customerOne.CustomerId)); + var orderTwo = OrderDataHelper.AddOrder(GetOrder(customerOne.CustomerId)); + + var customerTwo = CustomerDataHelper.Add(GetCustomer("FR")); + var orderThree = OrderDataHelper.AddOrder(GetOrder(customerTwo.CustomerId)); + + var customerOrderService = _container.Resolve(); + + // Act. + var results = customerOrderService.GetAllCustomerWithOrders(); + var customerOneResult = results.First(c => c.Customer.CustomerId == customerOne.CustomerId); + var customerTwoResult = results.First(c => c.Customer.CustomerId == customerTwo.CustomerId); + + // Assert. + Assert.AreEqual(2, customerOneResult.Orders.Count()); + Assert.AreEqual(1, customerTwoResult.Orders.Count()); + Assert.IsTrue(customerOneResult.Orders.Any(o => o.OrderId == orderOne.OrderId)); + Assert.IsTrue(customerOneResult.Orders.Any(o => o.OrderId == orderTwo.OrderId)); + Assert.IsTrue(customerTwoResult.Orders.Any(o => o.OrderId == orderThree.OrderId)); + } + + private Order GetOrder(int customerId) + { + return new Order + { + OrderId = new Random().Next(1, 200000000), + Amount = 100, + CustomerId = customerId, + VAT = 0 + }; + } + + private Customer GetCustomer(string country) + { + return new Customer + { + Country = country, + DateOfBirth = DateTime.UtcNow.AddDays(-20), + Name = "John Smith" + }; + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs index 05a5660..a1bd397 100644 --- a/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs +++ b/TechTest/AnyCompany.IntegrationTests/DataHelpers/OrderDataHelper.cs @@ -1,4 +1,5 @@ using System.Configuration; +using System.Data.Odbc; using System.Data.SqlClient; using System.Linq; using AnyCompany.Models; @@ -19,6 +20,24 @@ public static Order GetOrder(int id) } } + public static Order AddOrder(Order order) + { + using (var connection = new SqlConnection(GetConnectionString())) + { + connection.Open(); + connection.Query(@"INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId)", + new + { + OrderId = order.OrderId, + Amount = order.Amount, + VAT = order.VAT, + CustomerId = order.CustomerId + }); + + return order; + } + } + private static string GetConnectionString() { return ConfigurationManager.ConnectionStrings["OrderConnectionString"].ConnectionString; diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs index 45b4d42..307db03 100644 --- a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -2,6 +2,7 @@ using AnyCompany.IntegrationTests.DataHelpers; using AnyCompany.Ioc; using AnyCompany.Models; +using AnyCompany.Services.Constants; using AnyCompany.Services.Dtos; using AnyCompany.Services.Services; using Castle.Windsor; @@ -20,9 +21,9 @@ public void SetupFixture() _container = Bootstrapper.GetContainer(); } - [TestCase("UK")] - [TestCase("FR")] - public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) + [TestCase("UK", VatConstants.UkVat)] + [TestCase("FR", VatConstants.RowVat)] + public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country, double expectedVat) { // Arrange. var customer = CustomerDataHelper.Add(GetCustomer(country)); @@ -36,14 +37,18 @@ public void PlaceOrder_ShouldInsertAnOrder_AndReturnTrue(string country) // Assert. Assert.IsTrue(result); Assert.IsNotNull(insertedOrder); + Assert.AreEqual(customer.CustomerId, insertedOrder.CustomerId); + Assert.AreEqual(order.OrderId, insertedOrder.OrderId); + Assert.AreEqual(order.Amount, insertedOrder.Amount); + Assert.AreEqual(expectedVat, insertedOrder.VAT); } private OrderDto GetOrderDto() { return new OrderDto { - OrderId = new Random().Next(1, 2000000), // generate a random Id since the OrderId column is not an IDENTITY column - Amount = 200.99 + OrderId = new Random().Next(1, 200000000), // generate a random Id since the OrderId column is not an IDENTITY column + Amount = 200.99d }; } @@ -52,7 +57,7 @@ private Customer GetCustomer(string country) return new Customer { Country = country, - DateOfBirth = DateTime.UtcNow.AddDays(-20), + DateOfBirth = DateTime.UtcNow.AddYears(-41), Name = "John Smith" }; } diff --git a/TechTest/AnyCompany.Ioc/DependencyInstaller.cs b/TechTest/AnyCompany.Ioc/DependencyInstaller.cs index 4484969..f474f33 100644 --- a/TechTest/AnyCompany.Ioc/DependencyInstaller.cs +++ b/TechTest/AnyCompany.Ioc/DependencyInstaller.cs @@ -17,6 +17,9 @@ public void Install(IWindsorContainer container, IConfigurationStore store) container.Register( Component.For()); + container.Register( + Component.For()); + container.Register( Component.For()); From b690766a20d8e94f6513f00fbffbfd3fdf56e2c2 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sat, 10 Aug 2019 12:07:38 +0100 Subject: [PATCH 13/13] Minor refactoring of inline sql --- .../AnyCompany.Data.Dapper.csproj | 11 ++ .../Repositories/CustomerRepository.cs | 3 +- .../Repositories/CustomerRepositoryWrapper.cs | 5 +- .../Repositories/OrderRepository.cs | 5 +- .../Sql/SqlStatements.Designer.cs | 99 +++++++++++++ .../Sql/SqlStatements.resx | 132 ++++++++++++++++++ .../Helpers/VatCalculator.cs | 1 + .../Services/CustomerOrderService.cs | 1 + 8 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.Designer.cs create mode 100644 TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.resx diff --git a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj index 7dc1e8f..1a615fe 100644 --- a/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj +++ b/TechTest/AnyCompany.Data.Dapper/AnyCompany.Data.Dapper.csproj @@ -52,6 +52,11 @@ + + True + True + SqlStatements.resx + @@ -66,5 +71,11 @@ + + + ResXFileCodeGenerator + SqlStatements.Designer.cs + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs index 8ed1f6e..3fb998d 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepository.cs @@ -1,6 +1,7 @@ using System.Configuration; using System.Data.SqlClient; using System.Linq; +using AnyCompany.Data.Dapper.Sql; using AnyCompany.Models; using Dapper; @@ -14,7 +15,7 @@ public static Customer Load(int customerId) using (var connection = new SqlConnection(connectionString)) { connection.Open(); - return connection.Query("SELECT * FROM Customer WHERE CustomerId = @CustomerId", + return connection.Query(SqlStatements.LoadCustomerById, new {CustomerId = customerId}).FirstOrDefault(); } } diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs index aba79c0..e42be70 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/CustomerRepositoryWrapper.cs @@ -3,6 +3,7 @@ using AnyCompany.Data.Contract.Repositories; using AnyCompany.Data.Dapper.Enums; using AnyCompany.Data.Dapper.Factories; +using AnyCompany.Data.Dapper.Sql; using AnyCompany.Models; using Dapper; @@ -10,7 +11,7 @@ namespace AnyCompany.Data.Dapper.Repositories { public class CustomerRepositoryWrapper : ICustomerRepository { - private IConnectionFactory _connectionFactory; + private readonly IConnectionFactory _connectionFactory; public CustomerRepositoryWrapper(IConnectionFactory connectionFactory) { @@ -29,7 +30,7 @@ public IEnumerable GetList() using (var connection = _connectionFactory.Create(ConnectionType.CustomerDb)) { connection.Open(); - return connection.Query("SELECT CustomerId, Country, DateOfBirth, Name from dbo.Customer").ToList(); + return connection.Query(SqlStatements.GetAllCustomers).ToList(); } } } diff --git a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs index ff3ec0f..3320c23 100644 --- a/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs +++ b/TechTest/AnyCompany.Data.Dapper/Repositories/OrderRepository.cs @@ -4,6 +4,7 @@ using AnyCompany.Data.Contract.Repositories; using AnyCompany.Data.Dapper.Enums; using AnyCompany.Data.Dapper.Factories; +using AnyCompany.Data.Dapper.Sql; using AnyCompany.Models; using Dapper; @@ -23,7 +24,7 @@ public void Add(Order order) using (var connection = CreateConnection()) { connection.Open(); - connection.Execute(@"INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId)", + connection.Execute(SqlStatements.InsertOrder, new { OrderId = order.OrderId, @@ -39,7 +40,7 @@ public IEnumerable GetList() using (var connection = CreateConnection()) { connection.Open(); - return connection.Query("SELECT OrderId, Amount, VAT, CustomerId from dbo.Orders").ToList(); + return connection.Query(SqlStatements.GetAllOrders).ToList(); } } diff --git a/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.Designer.cs b/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.Designer.cs new file mode 100644 index 0000000..a085ca9 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AnyCompany.Data.Dapper.Sql { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SqlStatements { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SqlStatements() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AnyCompany.Data.Dapper.Sql.SqlStatements", typeof(SqlStatements).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to SELECT CustomerId, Country, DateOfBirth, Name from dbo.Customer. + /// + internal static string GetAllCustomers { + get { + return ResourceManager.GetString("GetAllCustomers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SELECT OrderId, Amount, VAT, CustomerId from dbo.Orders. + /// + internal static string GetAllOrders { + get { + return ResourceManager.GetString("GetAllOrders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId). + /// + internal static string InsertOrder { + get { + return ResourceManager.GetString("InsertOrder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SELECT * FROM Customer WHERE CustomerId = @CustomerId. + /// + internal static string LoadCustomerById { + get { + return ResourceManager.GetString("LoadCustomerById", resourceCulture); + } + } + } +} diff --git a/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.resx b/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.resx new file mode 100644 index 0000000..8f6fc44 --- /dev/null +++ b/TechTest/AnyCompany.Data.Dapper/Sql/SqlStatements.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SELECT CustomerId, Country, DateOfBirth, Name from dbo.Customer + + + SELECT OrderId, Amount, VAT, CustomerId from dbo.Orders + + + INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId) + + + SELECT * FROM Customer WHERE CustomerId = @CustomerId + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs b/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs index dbdd92b..ad73735 100644 --- a/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs +++ b/TechTest/AnyCompany.Services/Helpers/VatCalculator.cs @@ -4,6 +4,7 @@ namespace AnyCompany.Services.Helpers { internal static class VatCalculator { + // small bit of logic so kept in a static helper method - could move behind an interface in the future internal static double GetVat(string country) { return country == "UK" ? VatConstants.UkVat : VatConstants.RowVat; diff --git a/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs b/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs index 9a0dd87..1e1e31d 100644 --- a/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs +++ b/TechTest/AnyCompany.Services/Services/CustomerOrderService.cs @@ -25,6 +25,7 @@ public IEnumerable GetAllCustomerWithOrders() var customers = _customerRepository.GetList(); var orders = _orderRepository.GetList(); + // map orders to the appropriate customer and return a dto foreach (var customer in customers) { var customerOrderDto = new CustomerOrdersDto