From 595e25d4192b3bc9a2e80ed9a04ea55d8346be4e Mon Sep 17 00:00:00 2001 From: Meurig Freeman Date: Wed, 7 Aug 2019 20:40:26 +0100 Subject: [PATCH 1/3] Added stylecop, documented, added repository interfaces --- .../AnyCompany.Tests/AnyCompany.Tests.csproj | 40 +++++++------ TechTest/AnyCompany.Tests/Class1.cs | 11 ++-- .../Properties/AssemblyInfo.cs | 7 ++- TechTest/AnyCompany.Tests/packages.config | 4 ++ TechTest/AnyCompany/AnyCompany.csproj | 26 +++++++-- TechTest/AnyCompany/Customer.cs | 13 ----- TechTest/AnyCompany/CustomerRepository.cs | 33 ----------- TechTest/AnyCompany/Model/Customer.cs | 29 ++++++++++ TechTest/AnyCompany/Model/Order.cs | 27 +++++++++ TechTest/AnyCompany/Order.cs | 9 --- TechTest/AnyCompany/OrderRepository.cs | 25 -------- TechTest/AnyCompany/OrderService.cs | 24 -------- .../AnyCompany/Properties/AssemblyInfo.cs | 7 ++- .../Repository/CustomerRepository.cs | 51 +++++++++++++++++ .../Repository/CustomerRepositoryInstance.cs | 23 ++++++++ .../Repository/ICustomerRepository.cs | 19 +++++++ .../AnyCompany/Repository/IOrderRepository.cs | 18 ++++++ .../AnyCompany/Repository/OrderRepository.cs | 36 ++++++++++++ .../AnyCompany/Service/CustomerService.cs | 25 ++++++++ TechTest/AnyCompany/Service/OrderService.cs | 57 +++++++++++++++++++ TechTest/AnyCompany/packages.config | 4 ++ TechTest/stylecop.json | 16 ++++++ 22 files changed, 369 insertions(+), 135 deletions(-) create mode 100644 TechTest/AnyCompany.Tests/packages.config delete mode 100644 TechTest/AnyCompany/Customer.cs delete mode 100644 TechTest/AnyCompany/CustomerRepository.cs create mode 100644 TechTest/AnyCompany/Model/Customer.cs create mode 100644 TechTest/AnyCompany/Model/Order.cs delete mode 100644 TechTest/AnyCompany/Order.cs delete mode 100644 TechTest/AnyCompany/OrderRepository.cs delete mode 100644 TechTest/AnyCompany/OrderService.cs create mode 100644 TechTest/AnyCompany/Repository/CustomerRepository.cs create mode 100644 TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs create mode 100644 TechTest/AnyCompany/Repository/ICustomerRepository.cs create mode 100644 TechTest/AnyCompany/Repository/IOrderRepository.cs create mode 100644 TechTest/AnyCompany/Repository/OrderRepository.cs create mode 100644 TechTest/AnyCompany/Service/CustomerService.cs create mode 100644 TechTest/AnyCompany/Service/OrderService.cs create mode 100644 TechTest/AnyCompany/packages.config create mode 100644 TechTest/stylecop.json diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index b537fc2..0ca0853 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -1,10 +1,10 @@ - + Debug AnyCPU - cd5d577e-bdc9-4dfc-ac6a-b1da474995f3 + {CD5D577E-BDC9-4DFC-AC6A-B1DA474995F3} Library Properties AnyCompany.Tests @@ -20,6 +20,8 @@ DEBUG;TRACE prompt 4 + SA1208 + bin\Debug\AnyCompany.Tests.xml pdbonly @@ -30,24 +32,28 @@ 4 - - - - - - - - - - - - - - + + + + + + + + + + + stylecop.json + + + + + + + - + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/Class1.cs b/TechTest/AnyCompany.Tests/Class1.cs index 5957505..aeba4a8 100644 --- a/TechTest/AnyCompany.Tests/Class1.cs +++ b/TechTest/AnyCompany.Tests/Class1.cs @@ -1,11 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// +// Copyright © Investec Bank 2018 +// namespace AnyCompany.Tests { + /// + /// A test class. + /// public class Class1 { } diff --git a/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs index 726eefa..ea5eabc 100644 --- a/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs +++ b/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs @@ -1,5 +1,8 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// +// Copyright © Investec Bank 2018 +// + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/TechTest/AnyCompany.Tests/packages.config b/TechTest/AnyCompany.Tests/packages.config new file mode 100644 index 0000000..7887250 --- /dev/null +++ b/TechTest/AnyCompany.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index 5b0498d..ba007c4 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -20,6 +20,8 @@ DEBUG;TRACE prompt 4 + SA1208 + bin\Debug\AnyCompany.xml pdbonly @@ -40,12 +42,26 @@ - - - - - + + + + + + + + + + + + stylecop.json + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/Customer.cs b/TechTest/AnyCompany/Customer.cs deleted file mode 100644 index aa994b6..0000000 --- a/TechTest/AnyCompany/Customer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace AnyCompany -{ - public class Customer - { - public string Country { get; set; } - - public DateTime DateOfBirth { get; set; } - - public string Name { get; set; } - } -} 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/Model/Customer.cs b/TechTest/AnyCompany/Model/Customer.cs new file mode 100644 index 0000000..8e5b938 --- /dev/null +++ b/TechTest/AnyCompany/Model/Customer.cs @@ -0,0 +1,29 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany +{ + using System; + + /// + /// The customer database model. + /// + public class Customer + { + /// + /// Gets or sets a description of the country in which the customer lives. + /// + public string Country { get; set; } + + /// + /// Gets or sets this customer's date of birth. + /// + public DateTime DateOfBirth { get; set; } + + /// + /// Gets or sets this customer's full name. + /// + public string Name { get; set; } + } +} diff --git a/TechTest/AnyCompany/Model/Order.cs b/TechTest/AnyCompany/Model/Order.cs new file mode 100644 index 0000000..69f9452 --- /dev/null +++ b/TechTest/AnyCompany/Model/Order.cs @@ -0,0 +1,27 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany +{ + /// + /// The order database model. + /// + public class Order + { + /// + /// Gets or sets a unique identifier for this order. + /// + public int OrderId { get; set; } + + /// + /// Gets or sets the amount (in GBP?) this order is for. + /// + public double Amount { get; set; } + + /// + /// Gets or sets the rate of VAT (Value Added Tax) due on this order. + /// + public double VAT { get; set; } + } +} diff --git a/TechTest/AnyCompany/Order.cs b/TechTest/AnyCompany/Order.cs deleted file mode 100644 index fec8e7b..0000000 --- a/TechTest/AnyCompany/Order.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AnyCompany -{ - public class Order - { - public int OrderId { 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/Properties/AssemblyInfo.cs b/TechTest/AnyCompany/Properties/AssemblyInfo.cs index bcdfb17..2de047e 100644 --- a/TechTest/AnyCompany/Properties/AssemblyInfo.cs +++ b/TechTest/AnyCompany/Properties/AssemblyInfo.cs @@ -1,5 +1,8 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// +// Copyright © Investec Bank 2018 +// + +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/TechTest/AnyCompany/Repository/CustomerRepository.cs b/TechTest/AnyCompany/Repository/CustomerRepository.cs new file mode 100644 index 0000000..b7fd142 --- /dev/null +++ b/TechTest/AnyCompany/Repository/CustomerRepository.cs @@ -0,0 +1,51 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Repository +{ + using System; + using System.Data.SqlClient; + + /// + /// This static class has been explicitly requested to remain in place. It provides the core functionality for + /// . + /// + internal static class CustomerRepository + { + /// + /// The connection string to use for connecting to the database. + /// + private static readonly string ConnectionString = + @"Data Source=(local);Database=Customers;User Id=admin;Password=password;"; + + /// + /// Loads a cutomer by it's customerId. + /// + /// The id of the customer to load. + /// The customer with the given id, if one exists. + 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/Repository/CustomerRepositoryInstance.cs b/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs new file mode 100644 index 0000000..d01ef3d --- /dev/null +++ b/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs @@ -0,0 +1,23 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Repository +{ + /// + /// An instantiable wrapper for the , providing a SqlClient implementation of + /// . + /// + public class CustomerRepositoryInstance : ICustomerRepository + { + /// + /// Loads a cutomer by it's customerId. + /// + /// The id of the customer to load. + /// The customer with the given id, if one exists. + public Customer Load(int customerId) + { + return CustomerRepository.Load(customerId); + } + } +} diff --git a/TechTest/AnyCompany/Repository/ICustomerRepository.cs b/TechTest/AnyCompany/Repository/ICustomerRepository.cs new file mode 100644 index 0000000..2de1f97 --- /dev/null +++ b/TechTest/AnyCompany/Repository/ICustomerRepository.cs @@ -0,0 +1,19 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Repository +{ + /// + /// Provdies methods for loading (and in future saving) objects. + /// + public interface ICustomerRepository + { + /// + /// Loads a cutomer by it's customerId. + /// + /// The id of the customer to load. + /// The customer with the given id, if one exists. + Customer Load(int customerId); + } +} diff --git a/TechTest/AnyCompany/Repository/IOrderRepository.cs b/TechTest/AnyCompany/Repository/IOrderRepository.cs new file mode 100644 index 0000000..16a21e7 --- /dev/null +++ b/TechTest/AnyCompany/Repository/IOrderRepository.cs @@ -0,0 +1,18 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Repository +{ + /// + /// Provides methods for saving (and in future retrieving) orders. + /// + public interface IOrderRepository + { + /// + /// Saves a given order to the database. + /// + /// The order to save. + void Save(Order order); + } +} diff --git a/TechTest/AnyCompany/Repository/OrderRepository.cs b/TechTest/AnyCompany/Repository/OrderRepository.cs new file mode 100644 index 0000000..8a88b7d --- /dev/null +++ b/TechTest/AnyCompany/Repository/OrderRepository.cs @@ -0,0 +1,36 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Repository +{ + using System.Data.SqlClient; + + /// + /// Provides a SqlClient implementation of the interface. + /// + internal class OrderRepository : IOrderRepository + { + private static readonly string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; + + /// + /// Saves a given order to the database. + /// + /// The order to save. + 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/Service/CustomerService.cs b/TechTest/AnyCompany/Service/CustomerService.cs new file mode 100644 index 0000000..884c5e6 --- /dev/null +++ b/TechTest/AnyCompany/Service/CustomerService.cs @@ -0,0 +1,25 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Service +{ + using AnyCompany.Repository; + + /// + /// Contains business level logic for interacting with customers, specifically customer loading functionality. + /// + public class CustomerService + { + private readonly ICustomerRepository customerRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The customer repository. + public CustomerService(ICustomerRepository customerRepository) + { + this.customerRepository = customerRepository; + } + } +} diff --git a/TechTest/AnyCompany/Service/OrderService.cs b/TechTest/AnyCompany/Service/OrderService.cs new file mode 100644 index 0000000..5f32495 --- /dev/null +++ b/TechTest/AnyCompany/Service/OrderService.cs @@ -0,0 +1,57 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Service +{ + using AnyCompany.Repository; + + /// + /// Contains business level logic for interacting with orders, specifically providing order placing functionality. + /// + public class OrderService + { + private readonly IOrderRepository orderRepository; + private readonly ICustomerRepository customerRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The order repository. + /// The customer repository. + public OrderService(IOrderRepository orderRepository, ICustomerRepository customerRepository) + { + this.orderRepository = orderRepository; + this.customerRepository = customerRepository; + } + + /// + /// Attaches a given order to the customer with the given customerId. + /// + /// The order to be placed. + /// The customer to attach the oder to. + /// True if the order is places successfully, false otherwise. + public bool PlaceOrder(Order order, int customerId) + { + Customer customer = this.customerRepository.Load(customerId); + + if (order.Amount == 0) + { + return false; + } + + if (customer.Country == "UK") + { + order.VAT = 0.2d; + } + else + { + order.VAT = 0; + } + + this.orderRepository.Save(order); + + return true; + } + } +} diff --git a/TechTest/AnyCompany/packages.config b/TechTest/AnyCompany/packages.config new file mode 100644 index 0000000..7887250 --- /dev/null +++ b/TechTest/AnyCompany/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TechTest/stylecop.json b/TechTest/stylecop.json new file mode 100644 index 0000000..84f81e3 --- /dev/null +++ b/TechTest/stylecop.json @@ -0,0 +1,16 @@ +{ + // ACTION REQUIRED: This file was automatically added to your project, but it + // will not take effect until additional steps are taken to enable it. See the + // following page for additional information: + // + // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md + + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Investec Bank", + "copyrightText": "Copyright © Investec Bank 2018" + + } + } +} From 34d48dd679d6e7154c0b7afaf7ccadc998cb2caf Mon Sep 17 00:00:00 2001 From: Meurig Freeman Date: Thu, 8 Aug 2019 10:20:17 +0100 Subject: [PATCH 2/3] Added customer/order loading functionality and unit tests --- .../AnyCompany.Tests/AnyCompany.Tests.csproj | 40 +++++++- TechTest/AnyCompany.Tests/Class1.cs | 13 --- .../AnyCompany.Tests/CustomerServiceTests.cs | 98 +++++++++++++++++++ .../MockRepository/MockCustomerRepository.cs | 59 +++++++++++ .../MockRepository/MockOrderRepository.cs | 29 ++++++ .../AnyCompany.Tests/OrderServiceTests.cs | 78 +++++++++++++++ TechTest/AnyCompany.Tests/packages.config | 3 + TechTest/AnyCompany/AnyCompany.csproj | 5 + TechTest/AnyCompany/Model/Customer.cs | 5 + TechTest/AnyCompany/Model/Order.cs | 5 + .../Repository/CustomerRepository.cs | 54 +++++++++- .../Repository/CustomerRepositoryInstance.cs | 14 ++- .../Repository/ICustomerRepository.cs | 8 ++ .../AnyCompany/Repository/IOrderRepository.cs | 10 +- .../AnyCompany/Repository/OrderRepository.cs | 42 ++++++-- .../AnyCompany/Service/CustomerService.cs | 25 ++++- TechTest/AnyCompany/Service/OrderService.cs | 7 +- TechTest/AnyCompany/packages.config | 1 + 18 files changed, 460 insertions(+), 36 deletions(-) delete mode 100644 TechTest/AnyCompany.Tests/Class1.cs create mode 100644 TechTest/AnyCompany.Tests/CustomerServiceTests.cs create mode 100644 TechTest/AnyCompany.Tests/MockRepository/MockCustomerRepository.cs create mode 100644 TechTest/AnyCompany.Tests/MockRepository/MockOrderRepository.cs create mode 100644 TechTest/AnyCompany.Tests/OrderServiceTests.cs diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index 0ca0853..a742ad3 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -11,6 +12,14 @@ AnyCompany.Tests v4.6.1 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 @@ -22,6 +31,7 @@ 4 SA1208 bin\Debug\AnyCompany.Tests.xml + 7.1 pdbonly @@ -30,10 +40,20 @@ TRACE prompt 4 + 7.1 + + ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + @@ -42,7 +62,10 @@ - + + + + @@ -55,5 +78,20 @@ + + + {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.Tests/Class1.cs b/TechTest/AnyCompany.Tests/Class1.cs deleted file mode 100644 index aeba4a8..0000000 --- a/TechTest/AnyCompany.Tests/Class1.cs +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright © Investec Bank 2018 -// - -namespace AnyCompany.Tests -{ - /// - /// A test class. - /// - public class Class1 - { - } -} diff --git a/TechTest/AnyCompany.Tests/CustomerServiceTests.cs b/TechTest/AnyCompany.Tests/CustomerServiceTests.cs new file mode 100644 index 0000000..16d0fda --- /dev/null +++ b/TechTest/AnyCompany.Tests/CustomerServiceTests.cs @@ -0,0 +1,98 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Tests +{ + using AnyCompany.Service; + using AnyCompany.Tests.MockRepository; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Linq; + + /// + /// Provides unit tests for the class. + /// + [TestClass] + public class CustomerServiceTests + { + /// + /// Load all customers with orders + /// Some customers exist without orders + /// Get back the customers, no orders. + /// + [TestMethod] + public void LoadAllCustomerWithOrders_ExistingCustomersNoOrders_GetCustomersNoOrders() + { + // Arrange + var customerRepository = new MockCustomerRepository(); + var orderRepository = new MockOrderRepository(); + var customerService = new CustomerService(customerRepository, orderRepository); + + // Act + var customersWithOrders = customerService.LoadAllCustomersWithOrders(); + + // Assert + Assert.IsTrue(customersWithOrders.Count() == 2); + foreach (var (customer, orders) in customersWithOrders) + { + Assert.IsTrue(orders.Count() == 0); + } + } + + /// + /// Load all customers with orders + /// Some customers exist with orders + /// Get back the customers, with correct number of orders. + /// + [TestMethod] + public void LoadAllCustomerWithOrders_ExistingCustomersWithOrders_GetCustomersCorrectOrderNumbers() + { + // Arrange + var customerRepository = new MockCustomerRepository(); + var orderRepository = new MockOrderRepository(); + var customerService = new CustomerService(customerRepository, orderRepository); + var allCustomers = customerRepository.LoadAll().ToList(); + + orderRepository.Save(new Order() + { + OrderId = default, + Amount = 10, + VAT = default, + CustomerId = allCustomers[0].CustomerId, + }); + + orderRepository.Save(new Order() + { + OrderId = default, + Amount = 20, + VAT = default, + CustomerId = allCustomers[0].CustomerId, + }); + + orderRepository.Save(new Order() + { + OrderId = default, + Amount = 30, + VAT = default, + CustomerId = allCustomers[1].CustomerId, + }); + + // Act + var customersWithOrders = customerService.LoadAllCustomersWithOrders(); + + // Assert + Assert.IsTrue(customersWithOrders.Count() == 2); + foreach (var (customer, orders) in customersWithOrders) + { + if (customer.CustomerId == allCustomers[0].CustomerId) + { + Assert.IsTrue(orders.Count() == 2); + } + else if (customer.CustomerId == allCustomers[1].CustomerId) + { + Assert.IsTrue(orders.Count() == 1); + } + } + } + } +} diff --git a/TechTest/AnyCompany.Tests/MockRepository/MockCustomerRepository.cs b/TechTest/AnyCompany.Tests/MockRepository/MockCustomerRepository.cs new file mode 100644 index 0000000..c111a47 --- /dev/null +++ b/TechTest/AnyCompany.Tests/MockRepository/MockCustomerRepository.cs @@ -0,0 +1,59 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Tests.MockRepository +{ + using AnyCompany.Repository; + using System; + using System.Collections.Generic; + + /// + /// Provides a mock implementation of ICustomerRepository for use in unit tests, that starts with two customers. + /// + internal class MockCustomerRepository : ICustomerRepository + { + private readonly List customers = new List(); + + /// + /// Initializes a new instance of the class. + /// + public MockCustomerRepository() + { + this.customers.Add(new Customer + { + CustomerId = 1, + Country = "UK", + DateOfBirth = new DateTime(1990, 01, 01), + Name = "John Doe", + }); + + this.customers.Add(new Customer + { + CustomerId = 2, + Country = "USA", + DateOfBirth = new DateTime(1980, 01, 01), + Name = "Jane Doe", + }); + } + + /// + public Customer Load(int customerId) + { + if (customerId > 0 && customerId <= 2) + { + return this.customers[customerId - 1]; + } + else + { + throw new ApplicationException($"Customer not found with id: {customerId}"); + } + } + + /// + public IEnumerable LoadAll() + { + return this.customers; + } + } +} diff --git a/TechTest/AnyCompany.Tests/MockRepository/MockOrderRepository.cs b/TechTest/AnyCompany.Tests/MockRepository/MockOrderRepository.cs new file mode 100644 index 0000000..7945be6 --- /dev/null +++ b/TechTest/AnyCompany.Tests/MockRepository/MockOrderRepository.cs @@ -0,0 +1,29 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Tests.MockRepository +{ + using AnyCompany.Repository; + using System.Collections.Generic; + + /// + /// Provides a mock implementation of IOrderRepository for use during unit testing, that starts out empty. + /// + internal class MockOrderRepository : IOrderRepository + { + private readonly List orders = new List(); + + /// + public void Save(Order order) + { + this.orders.Add(order); + } + + /// + public IEnumerable LoadAll() + { + return this.orders; + } + } +} diff --git a/TechTest/AnyCompany.Tests/OrderServiceTests.cs b/TechTest/AnyCompany.Tests/OrderServiceTests.cs new file mode 100644 index 0000000..021b121 --- /dev/null +++ b/TechTest/AnyCompany.Tests/OrderServiceTests.cs @@ -0,0 +1,78 @@ +// +// Copyright © Investec Bank 2018 +// + +namespace AnyCompany.Tests +{ + using AnyCompany.Service; + using AnyCompany.Tests.MockRepository; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Linq; + + /// + /// Provides unit tests for the class. + /// + [TestClass] + public class OrderServiceTests + { + /// + /// Save + /// An order linked to a UK customer + /// VAT is 0.2d. + /// + [TestMethod] + public void PlaceOrder_LinkedToUkCustomer_VatIsTwentyPercent() + { + // Arrange + var orderRepository = new MockOrderRepository(); + var customerRepository = new MockCustomerRepository(); + var orderService = new OrderService(orderRepository, customerRepository); + var ukCustomer = customerRepository.LoadAll().First(); + + // Act + var order = new Order() + { + OrderId = default, + Amount = 20, + VAT = default, + CustomerId = ukCustomer.CustomerId, + }; + + orderService.PlaceOrder(order); + + // Assert + Assert.IsTrue(ukCustomer.Country == "UK"); + Assert.IsTrue(order.VAT == 0.2d); + } + + /// + /// Save + /// An order linked to a non-UK customer + /// VAT is 0. + /// + [TestMethod] + public void PlaceOrder_LinkedToNonUkCustomer_VatIsZero() + { + // Arrange + var orderRepository = new MockOrderRepository(); + var customerRepository = new MockCustomerRepository(); + var orderService = new OrderService(orderRepository, customerRepository); + var nonUkCustomer = customerRepository.LoadAll().Skip(1).First(); + + // Act + var order = new Order() + { + OrderId = default, + Amount = 20, + VAT = default, + CustomerId = nonUkCustomer.CustomerId, + }; + + orderService.PlaceOrder(order); + + // Assert + Assert.IsTrue(nonUkCustomer.Country != "UK"); + Assert.IsTrue(order.VAT == 0d); + } + } +} diff --git a/TechTest/AnyCompany.Tests/packages.config b/TechTest/AnyCompany.Tests/packages.config index 7887250..9a43b38 100644 --- a/TechTest/AnyCompany.Tests/packages.config +++ b/TechTest/AnyCompany.Tests/packages.config @@ -1,4 +1,7 @@  + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index ba007c4..b84844e 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -22,6 +22,7 @@ 4 SA1208 bin\Debug\AnyCompany.xml + 7.1 pdbonly @@ -30,10 +31,14 @@ TRACE prompt 4 + 7.1 + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + diff --git a/TechTest/AnyCompany/Model/Customer.cs b/TechTest/AnyCompany/Model/Customer.cs index 8e5b938..6029ccd 100644 --- a/TechTest/AnyCompany/Model/Customer.cs +++ b/TechTest/AnyCompany/Model/Customer.cs @@ -11,6 +11,11 @@ namespace AnyCompany /// public class Customer { + /// + /// Gets or sets the unique id for this customer. + /// + public int CustomerId { get; set; } + /// /// Gets or sets a description of the country in which the customer lives. /// diff --git a/TechTest/AnyCompany/Model/Order.cs b/TechTest/AnyCompany/Model/Order.cs index 69f9452..9865e17 100644 --- a/TechTest/AnyCompany/Model/Order.cs +++ b/TechTest/AnyCompany/Model/Order.cs @@ -23,5 +23,10 @@ public class Order /// Gets or sets the rate of VAT (Value Added Tax) due on this order. /// public double VAT { get; set; } + + /// + /// Gets or sets the id of the customer who placed this order. + /// + public int CustomerId { get; set; } } } diff --git a/TechTest/AnyCompany/Repository/CustomerRepository.cs b/TechTest/AnyCompany/Repository/CustomerRepository.cs index b7fd142..e08f0ca 100644 --- a/TechTest/AnyCompany/Repository/CustomerRepository.cs +++ b/TechTest/AnyCompany/Repository/CustomerRepository.cs @@ -5,6 +5,7 @@ namespace AnyCompany.Repository { using System; + using System.Collections.Generic; using System.Data.SqlClient; /// @@ -16,6 +17,7 @@ internal static class CustomerRepository /// /// The connection string to use for connecting to the database. /// + // TODO: The connection string should be built from injected config elements, not set statically in code. private static readonly string ConnectionString = @"Data Source=(local);Database=Customers;User Id=admin;Password=password;"; @@ -32,20 +34,62 @@ public static Customer Load(int customerId) connection.Open(); SqlCommand command = new SqlCommand( - "SELECT * FROM Customer WHERE CustomerId = " + customerId, + "SELECT CustomerId, Name, DateOfBirth, Country FROM Customer WHERE CustomerId = " + customerId, connection); var reader = command.ExecuteReader(); - while (reader.Read()) + if (!reader.HasRows) + { + throw new ApplicationException($"Customer not found with id: {customerId}"); + } + + reader.Read(); + + customer.CustomerId = int.Parse(reader["CustomerId"].ToString()); + customer.Name = reader["Name"].ToString(); + customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); + customer.Country = reader["Country"].ToString(); + + if (reader.Read()) { - customer.Name = reader["Name"].ToString(); - customer.DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()); - customer.Country = reader["Country"].ToString(); + throw new ApplicationException($"More than one customer found with id: {customerId}"); } connection.Close(); return customer; } + + /// + /// Loads all customers. + /// + /// The set of all customers. + public static IEnumerable LoadAll() + { + var customers = new List(); + + SqlConnection connection = new SqlConnection(ConnectionString); + connection.Open(); + + SqlCommand command = + new SqlCommand("SELECT CustomerId, Name, DateOfBirth, Country FROM Customer", connection); + var reader = command.ExecuteReader(); + + while (reader.Read()) + { + var customer = new Customer + { + CustomerId = int.Parse(reader["CustomerId"].ToString()), + Name = reader["Name"].ToString(), + DateOfBirth = DateTime.Parse(reader["DateOfBirth"].ToString()), + Country = reader["Country"].ToString(), + }; + customers.Add(customer); + } + + connection.Close(); + + return customers; + } } } diff --git a/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs b/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs index d01ef3d..8e1143c 100644 --- a/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs +++ b/TechTest/AnyCompany/Repository/CustomerRepositoryInstance.cs @@ -4,20 +4,24 @@ namespace AnyCompany.Repository { + using System.Collections.Generic; + /// /// An instantiable wrapper for the , providing a SqlClient implementation of /// . /// public class CustomerRepositoryInstance : ICustomerRepository { - /// - /// Loads a cutomer by it's customerId. - /// - /// The id of the customer to load. - /// The customer with the given id, if one exists. + /// public Customer Load(int customerId) { return CustomerRepository.Load(customerId); } + + /// + public IEnumerable LoadAll() + { + return CustomerRepository.LoadAll(); + } } } diff --git a/TechTest/AnyCompany/Repository/ICustomerRepository.cs b/TechTest/AnyCompany/Repository/ICustomerRepository.cs index 2de1f97..b616942 100644 --- a/TechTest/AnyCompany/Repository/ICustomerRepository.cs +++ b/TechTest/AnyCompany/Repository/ICustomerRepository.cs @@ -4,6 +4,8 @@ namespace AnyCompany.Repository { + using System.Collections.Generic; + /// /// Provdies methods for loading (and in future saving) objects. /// @@ -15,5 +17,11 @@ public interface ICustomerRepository /// The id of the customer to load. /// The customer with the given id, if one exists. Customer Load(int customerId); + + /// + /// Loads all customers. + /// + /// The set of all customers. + IEnumerable LoadAll(); } } diff --git a/TechTest/AnyCompany/Repository/IOrderRepository.cs b/TechTest/AnyCompany/Repository/IOrderRepository.cs index 16a21e7..2546487 100644 --- a/TechTest/AnyCompany/Repository/IOrderRepository.cs +++ b/TechTest/AnyCompany/Repository/IOrderRepository.cs @@ -4,8 +4,10 @@ namespace AnyCompany.Repository { + using System.Collections.Generic; + /// - /// Provides methods for saving (and in future retrieving) orders. + /// Provides methods for saving and retrieving orders. /// public interface IOrderRepository { @@ -14,5 +16,11 @@ public interface IOrderRepository /// /// The order to save. void Save(Order order); + + /// + /// Loads all orders. + /// + /// The set of all orders. + IEnumerable LoadAll(); } } diff --git a/TechTest/AnyCompany/Repository/OrderRepository.cs b/TechTest/AnyCompany/Repository/OrderRepository.cs index 8a88b7d..4761216 100644 --- a/TechTest/AnyCompany/Repository/OrderRepository.cs +++ b/TechTest/AnyCompany/Repository/OrderRepository.cs @@ -4,6 +4,7 @@ namespace AnyCompany.Repository { + using System.Collections.Generic; using System.Data.SqlClient; /// @@ -11,26 +12,55 @@ namespace AnyCompany.Repository /// internal class OrderRepository : IOrderRepository { - private static readonly string ConnectionString = @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; + // TODO: The connection string should be built from injected config elements, not set statically in code. + private static readonly string ConnectionString = + @"Data Source=(local);Database=Orders;User Id=admin;Password=password;"; - /// - /// Saves a given order to the database. - /// - /// The order to save. + /// public void Save(Order order) { SqlConnection connection = new SqlConnection(ConnectionString); connection.Open(); - SqlCommand command = new SqlCommand("INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT)", connection); + SqlCommand command = + new SqlCommand("INSERT INTO Orders VALUES (@OrderId, @Amount, @VAT, @CustomerId)", connection); command.Parameters.AddWithValue("@OrderId", order.OrderId); command.Parameters.AddWithValue("@Amount", order.Amount); command.Parameters.AddWithValue("@VAT", order.VAT); + command.Parameters.AddWithValue("@CustomerId", order.CustomerId); command.ExecuteNonQuery(); connection.Close(); } + + /// + public IEnumerable LoadAll() + { + var orders = new List(); + + SqlConnection connection = new SqlConnection(ConnectionString); + connection.Open(); + + SqlCommand command = new SqlCommand("SELECT OrderId, Amount, VAT, CustomerId FROM Orders", connection); + var reader = command.ExecuteReader(); + + while (reader.Read()) + { + var order = new Order + { + OrderId = int.Parse(reader["OrderId"].ToString()), + Amount = double.Parse(reader["Amount"].ToString()), + VAT = double.Parse(reader["VAT"].ToString()), + CustomerId = int.Parse(reader["CustomerId"].ToString()), + }; + orders.Add(order); + } + + connection.Close(); + + return orders; + } } } diff --git a/TechTest/AnyCompany/Service/CustomerService.cs b/TechTest/AnyCompany/Service/CustomerService.cs index 884c5e6..06f38e7 100644 --- a/TechTest/AnyCompany/Service/CustomerService.cs +++ b/TechTest/AnyCompany/Service/CustomerService.cs @@ -5,6 +5,8 @@ namespace AnyCompany.Service { using AnyCompany.Repository; + using System.Collections.Generic; + using System.Linq; /// /// Contains business level logic for interacting with customers, specifically customer loading functionality. @@ -12,14 +14,35 @@ namespace AnyCompany.Service public class CustomerService { private readonly ICustomerRepository customerRepository; + private readonly IOrderRepository orderRepository; /// /// Initializes a new instance of the class. /// /// The customer repository. - public CustomerService(ICustomerRepository customerRepository) + /// The order repository. + public CustomerService(ICustomerRepository customerRepository, IOrderRepository orderRepository) { this.customerRepository = customerRepository; + this.orderRepository = orderRepository; + } + + /// + /// Loads all customers, along with their orders. + /// + /// A set of (customer, set of orders) tuples. + public IEnumerable<(Customer, IEnumerable)> LoadAllCustomersWithOrders() + { + var allCustomers = this.customerRepository.LoadAll(); + var allOrders = this.orderRepository.LoadAll(); + + var customerIdToOrdersMapping = allCustomers.ToDictionary(c => c.CustomerId, c => new List()); + foreach (var order in allOrders) + { + customerIdToOrdersMapping[order.CustomerId].Add(order); + } + + return allCustomers.Select(c => (c, customerIdToOrdersMapping[c.CustomerId].AsEnumerable())); } } } diff --git a/TechTest/AnyCompany/Service/OrderService.cs b/TechTest/AnyCompany/Service/OrderService.cs index 5f32495..ed86f9a 100644 --- a/TechTest/AnyCompany/Service/OrderService.cs +++ b/TechTest/AnyCompany/Service/OrderService.cs @@ -29,11 +29,10 @@ public OrderService(IOrderRepository orderRepository, ICustomerRepository custom /// Attaches a given order to the customer with the given customerId. /// /// The order to be placed. - /// The customer to attach the oder to. - /// True if the order is places successfully, false otherwise. - public bool PlaceOrder(Order order, int customerId) + /// True if the order is placed successfully, false otherwise. + public bool PlaceOrder(Order order) { - Customer customer = this.customerRepository.Load(customerId); + Customer customer = this.customerRepository.Load(order.CustomerId); if (order.Amount == 0) { diff --git a/TechTest/AnyCompany/packages.config b/TechTest/AnyCompany/packages.config index 7887250..530e2bd 100644 --- a/TechTest/AnyCompany/packages.config +++ b/TechTest/AnyCompany/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file From c81f5764635185e772fafe23e753d0fce161f1fb Mon Sep 17 00:00:00 2001 From: Meurig Freeman Date: Thu, 8 Aug 2019 10:35:30 +0100 Subject: [PATCH 3/3] Cleaned up stylecop settings file --- TechTest/stylecop.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/TechTest/stylecop.json b/TechTest/stylecop.json index 84f81e3..ead6185 100644 --- a/TechTest/stylecop.json +++ b/TechTest/stylecop.json @@ -1,10 +1,4 @@ { - // ACTION REQUIRED: This file was automatically added to your project, but it - // will not take effect until additional steps are taken to enable it. See the - // following page for additional information: - // - // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "documentationRules": {