diff --git a/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj new file mode 100644 index 0000000..2bd951e --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/AnyCompany.IntegrationTests.csproj @@ -0,0 +1,79 @@ + + + + + + Debug + AnyCPU + {06DF4A00-EA47-467D-860E-DD0F949ABDC3} + Library + Properties + AnyCompany.IntegrationTests + AnyCompany.IntegrationTests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.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/CreateSeededDb.Sql b/TechTest/AnyCompany.IntegrationTests/CreateSeededDb.Sql new file mode 100644 index 0000000..0e743ff --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/CreateSeededDb.Sql @@ -0,0 +1,47 @@ +CREATE DATABASE [Customers] +GO + +CREATE TABLE [Customers].[dbo].[Customer]( + [CustomerId] [int] NOT NULL, + [Country] [nvarchar](3) NOT NULL, + [DateOfBirth] [date] NOT NULL, + [Name] [nvarchar](50) NOT NULL, + CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED +( + [CustomerId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO + +INSERT INTO [Customers].[dbo].[Customer] +([CustomerId],[Country],[DateOfBirth],[Name]) +VALUES +(1, 'ZA', '1980-04-16', 'bob'), +(2, 'ZA', '1991-09-23', 'jill'), +(3, 'UK', '1985-03-01', 'sarah'); + + +CREATE DATABASE [Orders] +GO + +CREATE TABLE [Orders].[dbo].[Orders]( + [OrderId] [int] NOT NULL, + [CustomerId] [int] NOT NULL, + [Amount] [float] NOT NULL, + [VAT] [float] NOT NULL, + CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED +( + [OrderId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +GO + +INSERT INTO [Orders].[dbo].[Orders] +([OrderId] ,[CustomerId] ,[Amount] ,[VAT]) +VALUES +(1, 1, 50, 0), +(2, 1, 60, 0), +(3, 2, 70, 0), +(4, 2, 80, 0), +(5, 3, 90, 0.2), +(6, 4, 100, 0.2); \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs new file mode 100644 index 0000000..a651404 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/CustomerServiceIntegrationTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AnyCompany.IntegrationTests +{ + [TestClass] + public class CustomerServiceIntegrationTests + { + private ICustomerService _customerService; + + public CustomerServiceIntegrationTests() + { + Config config = new Config(); + + var customerRepository = new CustomerRepositoryProxy(config.CustomerDbConnectionString); + + var orderRepository = new OrderRepository(config.OrdersDbConnectionString); + + _customerService = new CustomerService(orderRepository, customerRepository); + } + + [TestMethod] + public void GetAllCustomersWithOrders_Successfully() + { + // Arrange + + // Act + var customers = _customerService.GetAllCustomersWithOrders(); + + // Assert + Assert.IsTrue(customers.Count() > 0); + foreach (var customer in customers) + { + Assert.IsTrue(customer.Orders.Count() > 0); + } + } + + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs new file mode 100644 index 0000000..9c3c5b7 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/OrderServiceIntegrationTests.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AnyCompany.IntegrationTests +{ + [TestClass] + public class OrderServiceIntegrationTests + { + private IOrderService _orderService; + + public OrderServiceIntegrationTests() + { + Config config = new Config(); + + var customerRepository = new CustomerRepositoryProxy(config.CustomerDbConnectionString); + + var orderRepository = new OrderRepository(config.OrdersDbConnectionString); + + _orderService = new OrderService(orderRepository, customerRepository); + } + + [TestMethod] + public void PlaceOrder_Successfully() + { + // Arrange + Order newOrder = new Order() + { + CustomerId = 1, + OrderId = 100, + Amount = 100, + VAT = 100 + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(newOrder, 1); + + // Assert + Assert.AreEqual(true, isOrderPlaced); + } + } +} diff --git a/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4f0472e --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("AnyCompany.IntegrationTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.IntegrationTests")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("06df4a00-ea47-467d-860e-dd0f949abdc3")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.IntegrationTests/Readme.txt b/TechTest/AnyCompany.IntegrationTests/Readme.txt new file mode 100644 index 0000000..1ffcdd2 --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/Readme.txt @@ -0,0 +1,13 @@ + + +====== Sql Container ====== +docker run -e ACCEPT_EULA=Y -e SA_PASSWORD=zaq1ZAQ! -p 1434:1433 -d --name sqlserver --network testnet --net-alias ss mcr.microsoft.com/mssql/server:2017-CU8-ubuntu + +Connect to sql db and Run CreateSeededDb.Sql +server name: 127.0.0.1,1434 +login: sa +password: zaq1ZAQ! + +The above steps need to be scripted +Docker compose can start up db, migrations, SUT +So that everything is in correct state for integration tests \ No newline at end of file diff --git a/TechTest/AnyCompany.IntegrationTests/packages.config b/TechTest/AnyCompany.IntegrationTests/packages.config new file mode 100644 index 0000000..0d63e3f --- /dev/null +++ b/TechTest/AnyCompany.IntegrationTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj deleted file mode 100644 index b537fc2..0000000 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Debug - AnyCPU - cd5d577e-bdc9-4dfc-ac6a-b1da474995f3 - Library - Properties - AnyCompany.Tests - AnyCompany.Tests - v4.6.1 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - 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/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 726eefa..0000000 --- a/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -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.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Investec Bank")] -[assembly: AssemblyProduct("AnyCompany.Tests")] -[assembly: AssemblyCopyright("Copyright © Investec Bank 2018")] -[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("cd5d577e-bdc9-4dfc-ac6a-b1da474995f3")] - -// 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.UnitTest/AnyCompany.UnitTest.csproj b/TechTest/AnyCompany.UnitTest/AnyCompany.UnitTest.csproj new file mode 100644 index 0000000..2d36aad --- /dev/null +++ b/TechTest/AnyCompany.UnitTest/AnyCompany.UnitTest.csproj @@ -0,0 +1,75 @@ + + + + + + Debug + AnyCPU + {EDE25C74-5461-46EA-8FB0-A53F4AE196C2} + Library + Properties + AnyCompany.UnitTest + AnyCompany.UnitTest + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.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.UnitTest/CustomerServiceTests.cs b/TechTest/AnyCompany.UnitTest/CustomerServiceTests.cs new file mode 100644 index 0000000..ca60e30 --- /dev/null +++ b/TechTest/AnyCompany.UnitTest/CustomerServiceTests.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AnyCompany.UnitTest +{ + [TestClass] + public class CustomerServiceTests + { + private OrderRepositoryMock _orderRepository; + private CustomerRepositoryMock _customerRepository; + private ICustomerService _customerService; + + public CustomerServiceTests() + { + var customerId1 = 1; + var customerId2 = 2; + + _customerRepository = new CustomerRepositoryMock + { + Customers = new List() + { + new Customer() + { + CustomerId = customerId1, + Country = "ZA", + Name = "bob", + DateOfBirth = new DateTime(1980, 01, 06) + }, + new Customer() + { + CustomerId = customerId2, + Country = "uk", + Name = "jill", + DateOfBirth = new DateTime(1980, 01, 06) + } + } + }; + + _orderRepository = new OrderRepositoryMock + { + Orders = new List() + { + new Order() + { + CustomerId = customerId1, + OrderId = 1, + Amount = 110, + VAT = 0 + }, + new Order() + { + CustomerId = customerId1, + OrderId = 2, + Amount = 100, + VAT = 0 + }, + new Order() + { + CustomerId = customerId1, + OrderId = 3, + Amount = 90, + VAT = 0.2 + }, + new Order() + { + CustomerId = customerId1, + OrderId = 4, + Amount = 80, + VAT = 0.2 + }, + } + }; + + _customerService = new CustomerService(_orderRepository, _customerRepository); + } + + [TestMethod] + public void GetAllCustomersWithOrders_Successfully() + { + // Arrange + + // Act + var customers = _customerService.GetAllCustomersWithOrders(); + + // Assert + Assert.AreEqual(_customerRepository.Customers.Count, customers.Count()); + Assert.AreEqual(_orderRepository.Orders.Count, customers.Sum(x => x.Orders.Count())); + } + + [TestMethod] + public void When_NoCustomersExist_Return_EmptyList() + { + // Arrange + _customerRepository.Customers = new List(); + + // Act + var customers = _customerService.GetAllCustomersWithOrders(); + + // Assert + Assert.AreEqual(0, customers.Count()); + } + + [TestMethod] + public void When_NoOrdersExist_Return_CustomersWith_EmptyOrdersList() + { + // Arrange + _orderRepository.Orders = new List(); + + // Act + var customers = _customerService.GetAllCustomersWithOrders(); + + // Assert + Assert.AreEqual(_customerRepository.Customers.Count, customers.Count()); + Assert.AreEqual(0, customers.Sum(x => x.Orders.Count())); + } + + } +} diff --git a/TechTest/AnyCompany.UnitTest/OrderServiceTests.cs b/TechTest/AnyCompany.UnitTest/OrderServiceTests.cs new file mode 100644 index 0000000..4922340 --- /dev/null +++ b/TechTest/AnyCompany.UnitTest/OrderServiceTests.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AnyCompany.UnitTest +{ + [TestClass] + public class OrderServiceTests + { + private OrderRepositoryMock _orderRepository; + private CustomerRepositoryMock _customerRepository; + private IOrderService _orderService; + + public OrderServiceTests() + { + _orderRepository = new OrderRepositoryMock(); + _customerRepository = new CustomerRepositoryMock(); + _orderService = new OrderService(_orderRepository, _customerRepository); + } + + [TestMethod] + public void PlaceOrder_Successfully() + { + // Arrange + Customer customer = new Customer() + { + CustomerId = 1, + Country = "ZA", + Name = "bob", + DateOfBirth = new DateTime(1980, 01, 06) + }; + _customerRepository.Customers = new List() + { + customer + }; + + Order newOrder = new Order() + { + CustomerId = customer.CustomerId, + OrderId = 1, + Amount = 100, + VAT = 100 + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(newOrder, customer.CustomerId); + + // Assert + Assert.AreEqual(true, isOrderPlaced); + + var savedOrder = _orderRepository.Orders.SingleOrDefault(x => x.CustomerId == newOrder.CustomerId && x.OrderId == newOrder.OrderId); + Assert.AreEqual(newOrder.Amount, savedOrder.Amount); + Assert.AreEqual(0, savedOrder.VAT); + } + + [TestMethod] + public void PlaceOrder_Successfully_UkCustomer() + { + // Arrange + Customer customer = new Customer() + { + CustomerId = 1, + Country = "UK", + Name = "bob", + DateOfBirth = new DateTime(1980, 01, 06) + }; + _customerRepository.Customers = new List() + { + customer + }; + + Order newOrder = new Order() + { + CustomerId = customer.CustomerId, + OrderId = 1, + Amount = 100, + VAT = 100 + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(newOrder, customer.CustomerId); + + // Assert + Assert.AreEqual(true, isOrderPlaced); + + var savedOrder = _orderRepository.Orders.SingleOrDefault(x => x.CustomerId == newOrder.CustomerId && x.OrderId == newOrder.OrderId); + Assert.AreEqual(newOrder.Amount, savedOrder.Amount); + Assert.AreEqual(0.2d, savedOrder.VAT); + } + + [TestMethod] + public void When_OrderAmount_Is0_Return_False() + { + // Arrange + Customer customer = new Customer() + { + CustomerId = 1, + Country = "UK", + Name = "bob", + DateOfBirth = new DateTime(1980, 01, 06) + }; + _customerRepository.Customers = new List() + { + customer + }; + + Order newOrder = new Order() + { + CustomerId = customer.CustomerId, + OrderId = 1, + Amount = 0, + VAT = 100 + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(newOrder, customer.CustomerId); + + // Assert + Assert.AreEqual(false, isOrderPlaced); + + var savedOrder = _orderRepository.Orders.SingleOrDefault(x => x.CustomerId == newOrder.CustomerId && x.OrderId == newOrder.OrderId); + Assert.IsNull(savedOrder); + } + + [TestMethod] + public void When_Order_IsNull_Return_False() + { + // Arrange + Customer customer = new Customer() + { + CustomerId = 1, + Country = "UK", + Name = "bob", + DateOfBirth = new DateTime(1980, 01, 06) + }; + _customerRepository.Customers = new List() + { + customer + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(null, customer.CustomerId); + + // Assert + Assert.AreEqual(false, isOrderPlaced); + } + + [TestMethod] + public void When_Customer_NotFound_Return_False() + { + // Arrange + _customerRepository.Customers = new List(); + + Order newOrder = new Order() + { + CustomerId = 1, + OrderId = 1, + Amount = 100, + VAT = 100 + }; + + // Act + var isOrderPlaced = _orderService.PlaceOrder(newOrder, newOrder.CustomerId); + + // Assert + Assert.AreEqual(false, isOrderPlaced); + + var savedOrder = _orderRepository.Orders.SingleOrDefault(x => x.CustomerId == newOrder.CustomerId && x.OrderId == newOrder.OrderId); + Assert.IsNull(savedOrder); + } + } +} diff --git a/TechTest/AnyCompany.UnitTest/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.UnitTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a3e101c --- /dev/null +++ b/TechTest/AnyCompany.UnitTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("AnyCompany.UnitTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AnyCompany.UnitTest")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("ede25c74-5461-46ea-8fb0-a53f4ae196c2")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TechTest/AnyCompany.UnitTest/packages.config b/TechTest/AnyCompany.UnitTest/packages.config new file mode 100644 index 0000000..0d63e3f --- /dev/null +++ b/TechTest/AnyCompany.UnitTest/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index 5b0498d..384599f 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -40,11 +40,20 @@ - - - - - + + + + + + + + + + + + + + diff --git a/TechTest/AnyCompany/Config.cs b/TechTest/AnyCompany/Config.cs new file mode 100644 index 0000000..dc8564c --- /dev/null +++ b/TechTest/AnyCompany/Config.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AnyCompany +{ + public class Config + { + public string OrdersDbConnectionString { get; set; } = @"Server=(localdb)\mssqllocaldb;Database=Orders;Integrated Security=true"; + public string CustomerDbConnectionString { get; set; } = @"Server=(localdb)\mssqllocaldb;Database=Customers;Integrated Security=true"; + } +} diff --git a/TechTest/AnyCompany/CustomerRepository.cs b/TechTest/AnyCompany/CustomerRepository.cs deleted file mode 100644 index e3de9b7..0000000 --- a/TechTest/AnyCompany/CustomerRepository.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Data.SqlClient; - -namespace AnyCompany -{ - 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()) - { - customer.Name = reader["Name"].ToString(); - customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); - customer.Country = reader["Country"].ToString(); - } - - connection.Close(); - - return customer; - } - } -} diff --git a/TechTest/AnyCompany/Infrastructure/CustomerRepository.cs b/TechTest/AnyCompany/Infrastructure/CustomerRepository.cs new file mode 100644 index 0000000..beb673d --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/CustomerRepository.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; + +namespace AnyCompany +{ + public static class CustomerRepository + { + public static string ConnectionString; + + public static Customer Load(int customerId) + { + Customer customer = new Customer(); + + using (SqlConnection connection = new SqlConnection(ConnectionString)) + using (SqlCommand command = new SqlCommand("SELECT * FROM Customer WHERE CustomerId = @CustomerId", connection)) + { + connection.Open(); + command.Parameters.AddWithValue("@CustomerId", customerId); + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + customer = ExtractCustomer(reader); + } + } + + connection.Close(); + } + + return customer; + } + + public static IEnumerable LoadAll() + { + List customers = new List(); + + using (SqlConnection connection = new SqlConnection(ConnectionString)) + using (SqlCommand command = new SqlCommand("SELECT * FROM Customer", connection)) + { + connection.Open(); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var customer = ExtractCustomer(reader); + customers.Add(customer); + } + } + connection.Close(); + } + return customers; + } + + private static Customer ExtractCustomer(SqlDataReader reader) + { + Customer customer = new Customer + { + CustomerId = (int)reader["CustomerId"], + Name = reader["Name"].ToString(), + DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()), + Country = reader["Country"].ToString() + }; + + return customer; + } + } +} diff --git a/TechTest/AnyCompany/Infrastructure/CustomerRepositoryProxy.cs b/TechTest/AnyCompany/Infrastructure/CustomerRepositoryProxy.cs new file mode 100644 index 0000000..2b50714 --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/CustomerRepositoryProxy.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AnyCompany +{ + public class CustomerRepositoryProxy : ICustomerRepository + { + + public CustomerRepositoryProxy(string connectionString) + { + CustomerRepository.ConnectionString = connectionString; + } + + public Customer Load(int customerId) + { + return CustomerRepository.Load(customerId); + } + + public IEnumerable LoadAll() + { + return CustomerRepository.LoadAll(); + } + } +} diff --git a/TechTest/AnyCompany/Infrastructure/Interfaces/ICustomerRepository.cs b/TechTest/AnyCompany/Infrastructure/Interfaces/ICustomerRepository.cs new file mode 100644 index 0000000..7cdc5ce --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/Interfaces/ICustomerRepository.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace AnyCompany +{ + public interface ICustomerRepository + { + Customer Load(int customerId); + IEnumerable LoadAll(); + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany/Infrastructure/Interfaces/IOrderRepository.cs b/TechTest/AnyCompany/Infrastructure/Interfaces/IOrderRepository.cs new file mode 100644 index 0000000..c1ae19e --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/Interfaces/IOrderRepository.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace AnyCompany +{ + public interface IOrderRepository + { + void Add(Order order); + Dictionary> LoadOrdersForCustomers(IEnumerable customerIds); + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany/Infrastructure/Mocks/CustomerRepositoryMock.cs b/TechTest/AnyCompany/Infrastructure/Mocks/CustomerRepositoryMock.cs new file mode 100644 index 0000000..27b32ed --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/Mocks/CustomerRepositoryMock.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AnyCompany +{ + public class CustomerRepositoryMock : ICustomerRepository + { + public List Customers; + + public CustomerRepositoryMock() + { + Customers = new List(); + } + + public Customer Load(int customerId) + { + return Customers.SingleOrDefault(x => x.CustomerId == customerId); + } + + public IEnumerable LoadAll() + { + return Customers; + } + } +} diff --git a/TechTest/AnyCompany/Infrastructure/Mocks/OrderRepositoryMock.cs b/TechTest/AnyCompany/Infrastructure/Mocks/OrderRepositoryMock.cs new file mode 100644 index 0000000..c473124 --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/Mocks/OrderRepositoryMock.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; + +namespace AnyCompany +{ + public class OrderRepositoryMock : IOrderRepository + { + public List Orders; + + public OrderRepositoryMock() + { + Orders = new List(); + } + + public void Add(Order order) + { + Orders.Add(order); + } + + public Dictionary> LoadOrdersForCustomers(IEnumerable customerIds) + { + Dictionary> dic = new Dictionary>(); + foreach (var customerId in customerIds) + { + var orders = Orders.Where(x => x.CustomerId == customerId).ToList(); + dic.Add(customerId, orders); + } + + return dic; + } + + } +} diff --git a/TechTest/AnyCompany/Infrastructure/OrderRepository.cs b/TechTest/AnyCompany/Infrastructure/OrderRepository.cs new file mode 100644 index 0000000..86f0c41 --- /dev/null +++ b/TechTest/AnyCompany/Infrastructure/OrderRepository.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; + +namespace AnyCompany +{ + public class OrderRepository : IOrderRepository + { + private static string ConnectionString; + + public OrderRepository(string connectionString) + { + ConnectionString = connectionString; + } + + // Changed name from Save to Add + // Because the word "Save" infers it may be an idempotent opertaion like an "upsert" + // the word "Add" is clearly tells what the method does + // Could have used "Insert" as well + public void Add(Order order) + { + using (SqlConnection connection = new SqlConnection(ConnectionString)) + using (SqlCommand command = new SqlCommand("INSERT INTO Orders ([OrderId],[CustomerId],[Amount],[VAT]) VALUES (@OrderId, @CustomerId, @Amount, @VAT)", connection)) + { + command.Parameters.AddWithValue("@OrderId", order.OrderId); + command.Parameters.AddWithValue("@CustomerId", order.CustomerId); + command.Parameters.AddWithValue("@Amount", order.Amount); + command.Parameters.AddWithValue("@VAT", order.VAT); + + connection.Open(); + + command.ExecuteNonQuery(); + + connection.Close(); + } + } + + public Dictionary> LoadOrdersForCustomers(IEnumerable customerIds) + { + Dictionary> orders = new Dictionary>(); + string customerIdsParam = string.Join("','", customerIds); + + using (SqlConnection connection = new SqlConnection(ConnectionString)) + using (SqlCommand command = new SqlCommand($"SELECT * FROM Orders WHERE CustomerId IN ('{customerIdsParam}')", connection)) + { + connection.Open(); + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var order = ExtractOrder(reader); + if (orders.ContainsKey(order.CustomerId)) + { + orders[order.CustomerId].Add(order); + } + else + { + orders.Add(order.CustomerId, new List() + { + order + }); + } + } + } + connection.Close(); + } + + return orders; + } + + private Order ExtractOrder(SqlDataReader reader) + { + Order order = new Order + { + OrderId = (int)reader["OrderId"], + CustomerId = (int)reader["CustomerId"], + Amount = (double)reader["Amount"], + VAT = (double)reader["VAT"] + }; + + return order; + } + + } +} diff --git a/TechTest/AnyCompany/Customer.cs b/TechTest/AnyCompany/Models/Customer.cs similarity index 60% rename from TechTest/AnyCompany/Customer.cs rename to TechTest/AnyCompany/Models/Customer.cs index aa994b6..8eb4c50 100644 --- a/TechTest/AnyCompany/Customer.cs +++ b/TechTest/AnyCompany/Models/Customer.cs @@ -1,13 +1,19 @@ using System; +using System.Collections.Generic; namespace AnyCompany { public class Customer { + public int CustomerId { get; set; } + public string Country { get; set; } public DateTime DateOfBirth { get; set; } public string Name { get; set; } + + public virtual IEnumerable Orders { get; set; } + } } diff --git a/TechTest/AnyCompany/Order.cs b/TechTest/AnyCompany/Models/Order.cs similarity index 80% rename from TechTest/AnyCompany/Order.cs rename to TechTest/AnyCompany/Models/Order.cs index fec8e7b..32d68e1 100644 --- a/TechTest/AnyCompany/Order.cs +++ b/TechTest/AnyCompany/Models/Order.cs @@ -3,6 +3,7 @@ public class Order { public int OrderId { get; set; } + public int CustomerId { get; set; } public double Amount { get; set; } public double VAT { get; set; } } diff --git a/TechTest/AnyCompany/OrderRepository.cs b/TechTest/AnyCompany/OrderRepository.cs deleted file mode 100644 index 3229885..0000000 --- a/TechTest/AnyCompany/OrderRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Data.SqlClient; - -namespace AnyCompany -{ - internal class OrderRepository - { - private static string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; - - public void Save(Order order) - { - 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(); - } - } -} diff --git a/TechTest/AnyCompany/OrderService.cs b/TechTest/AnyCompany/OrderService.cs deleted file mode 100644 index ebfb103..0000000 --- a/TechTest/AnyCompany/OrderService.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace AnyCompany -{ - public class OrderService - { - private readonly OrderRepository orderRepository = new OrderRepository(); - - public bool PlaceOrder(Order order, int customerId) - { - Customer customer = CustomerRepository.Load(customerId); - - if (order.Amount == 0) - return false; - - if (customer.Country == "UK") - order.VAT = 0.2d; - else - order.VAT = 0; - - orderRepository.Save(order); - - return true; - } - } -} diff --git a/TechTest/AnyCompany/Services/CustomerService.cs b/TechTest/AnyCompany/Services/CustomerService.cs new file mode 100644 index 0000000..ac057df --- /dev/null +++ b/TechTest/AnyCompany/Services/CustomerService.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; + +namespace AnyCompany +{ + public class CustomerService : ICustomerService + { + private readonly IOrderRepository _orderRepository; + private readonly ICustomerRepository _customerRepository; + + public CustomerService(IOrderRepository orderRepository, ICustomerRepository customerRepository) + { + _orderRepository = orderRepository; + _customerRepository = customerRepository; + } + + public IEnumerable GetAllCustomersWithOrders() + { + var customers = _customerRepository.LoadAll(); + if (customers == null) + { + return new List(); + } + + var ordersDictionary = _orderRepository.LoadOrdersForCustomers(customers.Select(x => x.CustomerId)); + + foreach (var customer in customers) + { + if (ordersDictionary.ContainsKey(customer.CustomerId)) + { + customer.Orders = ordersDictionary[customer.CustomerId]; + } + } + + return customers; + } + + } +} diff --git a/TechTest/AnyCompany/Services/Interfaces/ICustomerService.cs b/TechTest/AnyCompany/Services/Interfaces/ICustomerService.cs new file mode 100644 index 0000000..bc8ce55 --- /dev/null +++ b/TechTest/AnyCompany/Services/Interfaces/ICustomerService.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace AnyCompany +{ + public interface ICustomerService + { + IEnumerable GetAllCustomersWithOrders(); + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany/Services/Interfaces/IOrderService.cs b/TechTest/AnyCompany/Services/Interfaces/IOrderService.cs new file mode 100644 index 0000000..87381c9 --- /dev/null +++ b/TechTest/AnyCompany/Services/Interfaces/IOrderService.cs @@ -0,0 +1,7 @@ +namespace AnyCompany +{ + public interface IOrderService + { + bool PlaceOrder(Order order, int customerId); + } +} \ No newline at end of file diff --git a/TechTest/AnyCompany/Services/OrderService.cs b/TechTest/AnyCompany/Services/OrderService.cs new file mode 100644 index 0000000..82df1ae --- /dev/null +++ b/TechTest/AnyCompany/Services/OrderService.cs @@ -0,0 +1,36 @@ +namespace AnyCompany +{ + public class OrderService : IOrderService + { + private readonly IOrderRepository _orderRepository; + private readonly ICustomerRepository _customerRepository; + + public OrderService(IOrderRepository orderRepository, ICustomerRepository customerRepository) + { + _orderRepository = orderRepository; + _customerRepository = customerRepository; + } + + public bool PlaceOrder(Order order, int customerId) + { + if (order == null || order.Amount == 0) + return false; + + Customer customer = _customerRepository.Load(customerId); + if (customer == null) + { + return false; + } + + if (customer.Country == "UK") + order.VAT = 0.2d; + else + order.VAT = 0; + + _orderRepository.Add(order); + + return true; + } + + } +} diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 1c1c57a..f4d3b8f 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -1,17 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30104.148 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}" ProjectSection(SolutionItems) = preProject Instructions.txt = Instructions.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.UnitTest", "AnyCompany.UnitTest\AnyCompany.UnitTest.csproj", "{EDE25C74-5461-46EA-8FB0-A53F4AE196C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.IntegrationTests", "AnyCompany.IntegrationTests\AnyCompany.IntegrationTests.csproj", "{06DF4A00-EA47-467D-860E-DD0F949ABDC3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,10 +24,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 - {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 - {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3}.Release|Any CPU.Build.0 = Release|Any CPU + {EDE25C74-5461-46EA-8FB0-A53F4AE196C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDE25C74-5461-46EA-8FB0-A53F4AE196C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDE25C74-5461-46EA-8FB0-A53F4AE196C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDE25C74-5461-46EA-8FB0-A53F4AE196C2}.Release|Any CPU.Build.0 = Release|Any CPU + {06DF4A00-EA47-467D-860E-DD0F949ABDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06DF4A00-EA47-467D-860E-DD0F949ABDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06DF4A00-EA47-467D-860E-DD0F949ABDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06DF4A00-EA47-467D-860E-DD0F949ABDC3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE