diff --git a/Solution.Txt b/Solution.Txt new file mode 100644 index 0000000..2868323 --- /dev/null +++ b/Solution.Txt @@ -0,0 +1,38 @@ +- Solution is not fully completed however only completed to the extent to prove the approach and concept. +- Dependency injection is used heavily to make is losley coupled and easy to test/mock. +- "static" class declarations is not changed. +- Only basic test cases has been added to prove the point. + +CustomerDbContext - NEW: + 1. ADO.Net is replaced by EF + +IUnitOfWork/UnitOfWork - NEW: + 1. New interface and class has been added. + +CustomerOrder - NEW: + 1. Mapping object, this contains the customer and it's orders if it has any. + +VAT - NEW: + 1. This is introduced to follow OCP. + +App.config - NEW: + 1. Connection string is moved here. + +CustomerRepository: + 1. Multiline ADO.net code is replaced by EF + 2. Load All customers implemented + +OrderRepository: + 1. Code is refactored to make it clean, readable and testable. + +OrderService: + 1. Refactored to make is extensible/OCP, clean, readable and testable. + +Test - NEW: + 1. CustomerRepositoryTest + 2. OrderRepositoryTest + 3. UnitOfWorkTest + 4. OrderServiceTest + + + \ No newline at end of file diff --git a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj index b537fc2..1f98799 100644 --- a/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj +++ b/TechTest/AnyCompany.Tests/AnyCompany.Tests.csproj @@ -1,16 +1,25 @@ - - + + + Debug AnyCPU - cd5d577e-bdc9-4dfc-ac6a-b1da474995f3 + {1983942E-3221-4387-AE22-28B57C38ECCA} Library Properties AnyCompany.Tests AnyCompany.Tests - v4.6.1 + v4.7.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 @@ -30,24 +39,63 @@ 4 - - - - - - - - - - - - - - + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\Moq.4.10.1\lib\net45\Moq.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 + - + + + + + + + App.config + PreserveNewest + + + + + + {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/App.config b/TechTest/AnyCompany.Tests/App.config new file mode 100644 index 0000000..a6a2b7f --- /dev/null +++ b/TechTest/AnyCompany.Tests/App.config @@ -0,0 +1,2 @@ + + \ 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 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/CustomerRepositoryTest.cs b/TechTest/AnyCompany.Tests/CustomerRepositoryTest.cs new file mode 100644 index 0000000..3db2ad5 --- /dev/null +++ b/TechTest/AnyCompany.Tests/CustomerRepositoryTest.cs @@ -0,0 +1,70 @@ +using AnyCompany.Entity; +using AnyCompany.Repository; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AnyCompany.Tests +{ + [TestClass] + public class CustomerRepositoryTest + { + private List CustomerList = new List(); + private Mock unitOfWork = new Mock(); + + public CustomerRepositoryTest() + { + CustomerList.Add( + new Customer() + { + CustomerId = 1, + Country = "UK", + Name = "UKCustomer", + DateOfBirth = new DateTime(1970, 12, 23) + }); + CustomerList.Add( + new Customer() + { + CustomerId = 2, + Country = "US", + Name = "USCustomer", + DateOfBirth = new DateTime(1970, 12, 24) + }); + + unitOfWork.Setup(s => s.Customers).Returns(CustomerList); + } + + [TestMethod] + [TestCategory("Customer Repository: Positive Scenario only")] + public void Load_ReturnACustomer() + { + // Arrange + var expectedCustomer = CustomerList.SingleOrDefault(c => c.CustomerId == 1); + + // Act + var customer = CustomerRepository.Load(expectedCustomer.CustomerId, unitOfWork.Object); + + // Assert + unitOfWork.Verify(v => v.Customers, Times.Once); + Assert.AreEqual(expectedCustomer.CustomerId, customer.CustomerId); + } + + + [TestMethod] + [TestCategory("Customer Repository: Positive Scenario only")] + public void Load_ReturnAllCustomers() + { + // Arrange + var expectedCustomerCount = CustomerList.Count; + + // Act + var customers = CustomerRepository.LoadAll(unitOfWork.Object).ToList(); + + // Assert + unitOfWork.Verify(v => v.Customers, Times.Once); + Assert.AreEqual(expectedCustomerCount, customers.Count); + } + } +} diff --git a/TechTest/AnyCompany.Tests/OrderRepositoryTest.cs b/TechTest/AnyCompany.Tests/OrderRepositoryTest.cs new file mode 100644 index 0000000..c098add --- /dev/null +++ b/TechTest/AnyCompany.Tests/OrderRepositoryTest.cs @@ -0,0 +1,36 @@ +using AnyCompany.Entity; +using AnyCompany.Repository; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; + +namespace AnyCompany.Tests +{ + [TestClass] + public class OrderRepositoryTest + { + private Mock unitOfWork = new Mock(); + + public OrderRepositoryTest() + { + unitOfWork.Setup(s => s.Orders).Returns(new List()); + unitOfWork.Setup(s => s.SaveChangesAsync()); + } + + [TestMethod] + [TestCategory("Order Repository: Positive Scenario only")] + public void Save_SaveAnOrder() + { + // Arrange + var order = new Order() { CustomerId = 1, OrderId = 1, Amount = 1d, VAT = 0 }; + OrderRepository orderRepository = new OrderRepository(unitOfWork.Object); + + // Act + orderRepository.Save(order); + + // Assert + unitOfWork.Verify(v => v.Orders, Times.Once); + unitOfWork.Verify(v => v.SaveChangesAsync(), Times.Once); + } + } +} diff --git a/TechTest/AnyCompany.Tests/OrderServiceTest.cs b/TechTest/AnyCompany.Tests/OrderServiceTest.cs new file mode 100644 index 0000000..f841a88 --- /dev/null +++ b/TechTest/AnyCompany.Tests/OrderServiceTest.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using AnyCompany.Entity; +using AnyCompany.Repository; +using AnyCompany.Service; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace AnyCompany.Tests +{ + [TestClass] + public class OrderServiceTest + { + private List CustomerList = new List(); + private List VatList = new List(); + + private Mock unitOfWork = new Mock(); + + public OrderServiceTest() + { + CustomerList.Add( + new Customer() + { + CustomerId = 1, + Country = "UK", + Name = "UKCustomer", + DateOfBirth = new DateTime(1970, 12, 23) + }); + CustomerList.Add( + new Customer() + { + CustomerId = 2, + Country = "US", + Name = "USCustomer", + DateOfBirth = new DateTime(1970, 12, 24) + }); + + unitOfWork.Setup(s => s.Customers).Returns(CustomerList); + + // Add items to the VAT list + VatList.Add( + new VAT() + { + ApplyVat = .2d, + Country = "UK" + }); + VatList.Add(new VAT() + { + ApplyVat = 0d, + Country = "US" + }); + + unitOfWork.Setup(s => s.VATs).Returns(VatList); + unitOfWork.Setup(s => s.Orders).Returns(new List()); + unitOfWork.Setup(s => s.SaveChangesAsync()); + } + + [TestMethod] + [TestCategory("Order Service: Positive Scenario - UK Customer")] + public void Save_SaveAnOrderForUKCustomer() + { + // Arrange + bool expectedOutput = true; + var order = new Order() { CustomerId = 1, OrderId = 1, Amount = 1d, VAT = 0 }; + OrderService orderRepository = new OrderService(unitOfWork.Object); + + // Act + var actualOutput = orderRepository.PlaceOrder(order); + + // Assert + unitOfWork.Verify(v => v.Customers, Times.Once); + unitOfWork.Verify(v => v.VATs, Times.Once); + unitOfWork.Verify(v => v.Orders, Times.Once); + unitOfWork.Verify(v => v.SaveChangesAsync(), Times.Once); + Assert.AreEqual(expectedOutput, actualOutput); + } + + + [TestMethod] + [TestCategory("Order Service: Positive Scenario - US Customer")] + public void Save_SaveAnOrderForUSCustomer() + { + // Arrange + bool expectedOutput = true; + var order = new Order() { CustomerId = 2, OrderId = 2, Amount = 1d, VAT = 0 }; + OrderService orderRepository = new OrderService(unitOfWork.Object); + + // Act + var actualOutput = orderRepository.PlaceOrder(order); + + // Assert + unitOfWork.Verify(v => v.Customers, Times.Once); + unitOfWork.Verify(v => v.VATs, Times.Once); + unitOfWork.Verify(v => v.Orders, Times.Once); + unitOfWork.Verify(v => v.SaveChangesAsync(), Times.Once); + Assert.AreEqual(expectedOutput, actualOutput); + } + + + + [TestMethod] + [TestCategory("Order Service: Positive Scenario only")] + public void IsValidOrder_ReturnTrue() + { + // Arrange + bool expectedOutput = true; + var order = new Order() { CustomerId = 2, OrderId = 2, Amount = 1d, VAT = 0 }; + OrderService orderRepository = new OrderService(unitOfWork.Object); + + // Act + var actualOutput = orderRepository.IsValidOrder(order); + + // Assert + Assert.AreEqual(expectedOutput, actualOutput); + } + } +} diff --git a/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs b/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs index 726eefa..2410ae1 100644 --- a/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs +++ b/TechTest/AnyCompany.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,20 @@ -using System.Reflection; +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: AssemblyCompany("")] [assembly: AssemblyProduct("AnyCompany.Tests")] -[assembly: AssemblyCopyright("Copyright © Investec Bank 2018")] +[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("cd5d577e-bdc9-4dfc-ac6a-b1da474995f3")] +[assembly: Guid("1983942e-3221-4387-ae22-28b57c38ecca")] -// 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.Tests/UnitOfWorkTest.cs b/TechTest/AnyCompany.Tests/UnitOfWorkTest.cs new file mode 100644 index 0000000..9c2ca95 --- /dev/null +++ b/TechTest/AnyCompany.Tests/UnitOfWorkTest.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using AnyCompany.Context; +using AnyCompany.Entity; +using AnyCompany.Repository; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace AnyCompany.Tests +{ + [TestClass] + public class UnitOfWorkTest + { + private Mock mockContext = new Mock(); + + private static Mock> CreateDbSetMock(IEnumerable elements) where T : class + { + var elementsAsQueryable = elements.AsQueryable(); + var dbSetMock = new Mock>(); + + dbSetMock.As>().Setup(m => m.Provider).Returns(elementsAsQueryable.Provider); + dbSetMock.As>().Setup(m => m.Expression).Returns(elementsAsQueryable.Expression); + dbSetMock.As>().Setup(m => m.ElementType).Returns(elementsAsQueryable.ElementType); + dbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(elementsAsQueryable.GetEnumerator()); + + return dbSetMock; + } + + [TestMethod] + [TestCategory(" Unit Of Work: Positive Scenario only")] + public void Customers_ReturnAllCustomer() + { + // Arrange + var data = new List + { + new Customer(){ + CustomerId = 1, + Country = "UK", + Name = "UKCustomer", + DateOfBirth = new DateTime(1970, 12, 23) } + }; + + mockContext.Setup(s => s.Customers).Returns(CreateDbSetMock(data).Object); + + UnitOfWork unitOfWork = new UnitOfWork(mockContext.Object); + + // Act + var customers = unitOfWork.Customers; + + // Assert + mockContext.Verify(v => v.Customers, Times.Once()); + Assert.AreEqual(data.Count(), customers.Count); + } + + [TestMethod] + [TestCategory(" Unit Of Work: Positive Scenario only")] + public void Orders_ReturnAllOrder() + { + // Arrange + var data = new List + { + new Order(){ + CustomerId = 1, + OrderId = 1, + Amount=1d, + VAT=0 } + }; + + mockContext.Setup(s => s.Orders).Returns(CreateDbSetMock(data).Object); + + UnitOfWork unitOfWork = new UnitOfWork(mockContext.Object); + + // Act + var orders = unitOfWork.Orders; + + // Assert + mockContext.Verify(v => v.Orders, Times.Once()); + Assert.AreEqual(data.Count(), orders.Count); + } + + + [TestMethod] + [TestCategory(" Unit Of Work: Positive Scenario only")] + public void VATs_ReturnAllVat() + { + // Arrange + var data = new List + { + new VAT(){ + Country="UK", + ApplyVat=0.2d + } + }; + + mockContext.Setup(s => s.Vats).Returns(CreateDbSetMock(data).Object); + + UnitOfWork unitOfWork = new UnitOfWork(mockContext.Object); + + // Act + var vats = unitOfWork.VATs; + + // Assert + mockContext.Verify(v => v.Vats, Times.Once()); + Assert.AreEqual(data.Count(), vats.Count); + } + + [TestMethod] + [TestCategory(" Unit Of Work: Positive Scenario only")] + public void CustomerOrders_LoadAllCustomersAndTheirLinkedOrders() + { + // Arrange + var customer = new List + { + new Customer(){ + CustomerId = 1, + Country = "UK", + Name = "UKCustomer", + DateOfBirth = new DateTime(1970, 12, 23) }, + new Customer(){ + CustomerId = 2, + Country = "US", + Name = "USCustomer", + DateOfBirth = new DateTime(1970, 12, 20) } + }; + mockContext.Setup(s => s.Customers).Returns(CreateDbSetMock(customer).Object); + + var order = new List + { + new Order(){ + CustomerId = 1, + OrderId = 1, + Amount=1d, + VAT=0 } + }; + mockContext.Setup(s => s.Orders).Returns(CreateDbSetMock(order).Object); + + UnitOfWork unitOfWork = new UnitOfWork(mockContext.Object); + + // Act + var customerOrders = unitOfWork.CustomerOrders; + + // Assert + mockContext.Verify(v => v.Customers, Times.Once()); + mockContext.Verify(v => v.Orders, Times.Once()); + + // Total number of customers returned + Assert.AreEqual(customer.Count(), customerOrders.Count); + + // Total number of order for customer 1 + Assert.AreEqual(1, customerOrders.SingleOrDefault(c=>c.Customer.CustomerId==1).Orders.Count); + + // Total number of order for customer 2 + Assert.AreEqual(0, customerOrders.SingleOrDefault(c => c.Customer.CustomerId == 2).Orders.Count); + } + + } +} diff --git a/TechTest/AnyCompany.Tests/packages.config b/TechTest/AnyCompany.Tests/packages.config new file mode 100644 index 0000000..ec49d8a --- /dev/null +++ b/TechTest/AnyCompany.Tests/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/AnyCompany.csproj b/TechTest/AnyCompany/AnyCompany.csproj index 5b0498d..c9463ea 100644 --- a/TechTest/AnyCompany/AnyCompany.csproj +++ b/TechTest/AnyCompany/AnyCompany.csproj @@ -30,7 +30,15 @@ 4 + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + + + @@ -40,12 +48,24 @@ - - - - - + + + + + + + + + + + + + + PreserveNewest + Designer + + \ No newline at end of file diff --git a/TechTest/AnyCompany/App.config b/TechTest/AnyCompany/App.config new file mode 100644 index 0000000..08ae4ea --- /dev/null +++ b/TechTest/AnyCompany/App.config @@ -0,0 +1,20 @@ + + + + +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TechTest/AnyCompany/Context/CustomerDbContext.cs b/TechTest/AnyCompany/Context/CustomerDbContext.cs new file mode 100644 index 0000000..0969ac7 --- /dev/null +++ b/TechTest/AnyCompany/Context/CustomerDbContext.cs @@ -0,0 +1,14 @@ +using AnyCompany.Entity; +using System.Data.Entity; + +namespace AnyCompany.Context +{ + public class CustomerDbContext : DbContext + { + public virtual DbSet Customers { get; set; } + + public virtual DbSet Orders { get; set; } + + public virtual DbSet Vats { 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/Customer.cs b/TechTest/AnyCompany/Entity/Customer.cs similarity index 72% rename from TechTest/AnyCompany/Customer.cs rename to TechTest/AnyCompany/Entity/Customer.cs index aa994b6..b0b1069 100644 --- a/TechTest/AnyCompany/Customer.cs +++ b/TechTest/AnyCompany/Entity/Customer.cs @@ -1,9 +1,11 @@ using System; -namespace AnyCompany +namespace AnyCompany.Entity { public class Customer { + public int CustomerId { get; set; } + public string Country { get; set; } public DateTime DateOfBirth { get; set; } diff --git a/TechTest/AnyCompany/Entity/CustomerOrder.cs b/TechTest/AnyCompany/Entity/CustomerOrder.cs new file mode 100644 index 0000000..6e76578 --- /dev/null +++ b/TechTest/AnyCompany/Entity/CustomerOrder.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace AnyCompany.Entity +{ + public class CustomerOrder + { + public Customer Customer { get; set; } + + public List Orders { get; set; } + } +} diff --git a/TechTest/AnyCompany/Order.cs b/TechTest/AnyCompany/Entity/Order.cs similarity index 67% rename from TechTest/AnyCompany/Order.cs rename to TechTest/AnyCompany/Entity/Order.cs index fec8e7b..7925549 100644 --- a/TechTest/AnyCompany/Order.cs +++ b/TechTest/AnyCompany/Entity/Order.cs @@ -1,9 +1,13 @@ -namespace AnyCompany +namespace AnyCompany.Entity { 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/Entity/VAT.cs b/TechTest/AnyCompany/Entity/VAT.cs new file mode 100644 index 0000000..0fc7c37 --- /dev/null +++ b/TechTest/AnyCompany/Entity/VAT.cs @@ -0,0 +1,9 @@ +namespace AnyCompany.Entity +{ + public class VAT + { + public string Country { get; set; } + + public double ApplyVat { 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/Repository/CustomerRepository.cs b/TechTest/AnyCompany/Repository/CustomerRepository.cs new file mode 100644 index 0000000..f920c60 --- /dev/null +++ b/TechTest/AnyCompany/Repository/CustomerRepository.cs @@ -0,0 +1,19 @@ +using AnyCompany.Entity; +using System.Collections.Generic; +using System.Linq; + +namespace AnyCompany.Repository +{ + public static class CustomerRepository + { + public static Customer Load(int customerId, IUnitOfWork unitOfWork) + { + return unitOfWork.Customers.SingleOrDefault(c => c.CustomerId == customerId); + } + + public static IEnumerable LoadAll(IUnitOfWork unitOfWork) + { + return unitOfWork.Customers.ToList(); + } + } +} diff --git a/TechTest/AnyCompany/Repository/IUnitOfWork.cs b/TechTest/AnyCompany/Repository/IUnitOfWork.cs new file mode 100644 index 0000000..307848b --- /dev/null +++ b/TechTest/AnyCompany/Repository/IUnitOfWork.cs @@ -0,0 +1,18 @@ +using AnyCompany.Entity; +using System.Collections.Generic; + +namespace AnyCompany.Repository +{ + public interface IUnitOfWork + { + List Customers { get; } + + List Orders { get; } + + List VATs { get; } + + List CustomerOrders { get; } + + void SaveChangesAsync(); + } +} diff --git a/TechTest/AnyCompany/Repository/OrderRepository.cs b/TechTest/AnyCompany/Repository/OrderRepository.cs new file mode 100644 index 0000000..2044535 --- /dev/null +++ b/TechTest/AnyCompany/Repository/OrderRepository.cs @@ -0,0 +1,21 @@ +using AnyCompany.Entity; + +namespace AnyCompany.Repository +{ + public class OrderRepository + { + private readonly IUnitOfWork unitOfWork; + + public OrderRepository(IUnitOfWork UnitOfwork) + { + unitOfWork = UnitOfwork; + } + + public void Save(Order order) + { + unitOfWork.Orders.Add(order); + + unitOfWork.SaveChangesAsync(); + } + } +} diff --git a/TechTest/AnyCompany/Repository/UnitOfWork.cs b/TechTest/AnyCompany/Repository/UnitOfWork.cs new file mode 100644 index 0000000..6130a1a --- /dev/null +++ b/TechTest/AnyCompany/Repository/UnitOfWork.cs @@ -0,0 +1,73 @@ +using AnyCompany.Context; +using AnyCompany.Entity; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AnyCompany.Repository +{ + public class UnitOfWork : IUnitOfWork, IDisposable + { + private readonly CustomerDbContext customerDbContext; + + public UnitOfWork(CustomerDbContext dbContext) + { + customerDbContext = dbContext; + } + + public List Customers + { + get + { + return customerDbContext.Customers.ToList(); + } + } + + public List Orders + { + get + { + return customerDbContext.Orders.ToList(); + } + } + + public List VATs + { + get + { + return customerDbContext.Vats.ToList(); + } + } + + public List CustomerOrders + { + get + { + // Group orders by customer id + var ord = from o in Orders + group o by o.CustomerId into orderGroup + select new { CustomerId = orderGroup.Key, Orders = orderGroup.ToList() }; + + // Create CustomerOrder mapping object + var customerOrders = (from c in Customers + join o in ord on c.CustomerId equals o.CustomerId into cog + from p in cog.DefaultIfEmpty() + select new CustomerOrder() { Customer = c, Orders = p == null ? new List() : p.Orders }).ToList(); + + // return final list mapping object + return customerOrders; + } + } + + public async void SaveChangesAsync() + { + await customerDbContext.SaveChangesAsync(); + } + + public void Dispose() + { + customerDbContext?.Dispose(); + } + + } +} diff --git a/TechTest/AnyCompany/Service/OrderService.cs b/TechTest/AnyCompany/Service/OrderService.cs new file mode 100644 index 0000000..8612bc0 --- /dev/null +++ b/TechTest/AnyCompany/Service/OrderService.cs @@ -0,0 +1,53 @@ +using AnyCompany.Entity; +using AnyCompany.Repository; +using System.Linq; + +namespace AnyCompany.Service +{ + public class OrderService + { + private readonly OrderRepository orderRepository; + private readonly IUnitOfWork unitOfWork; + + public OrderService(IUnitOfWork UnitOfwork) + { + unitOfWork = UnitOfwork; + orderRepository = new OrderRepository(UnitOfwork); + } + + public bool PlaceOrder(Order order) + { + bool orderPlaced = false; + + if (IsValidOrder(order)) + { + try + { + Customer customer = CustomerRepository.Load(order.CustomerId, unitOfWork); + + var applicableVat = unitOfWork.VATs.SingleOrDefault(s => string.Compare(s.Country, customer.Country, true) == 0); + + if (applicableVat != null) + order.VAT = applicableVat.ApplyVat; + else + order.VAT = 0; + + orderRepository.Save(order); + + orderPlaced = true; + } + catch + { + //Logging and exception handling + } + } + + return orderPlaced; + } + + public bool IsValidOrder(Order order) + { + return order.Amount > 0; + } + } +} diff --git a/TechTest/AnyCompany/packages.config b/TechTest/AnyCompany/packages.config new file mode 100644 index 0000000..b3daf0d --- /dev/null +++ b/TechTest/AnyCompany/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TechTest/TechTest.sln b/TechTest/TechTest.sln index 1c1c57a..ad24ebd 100644 --- a/TechTest/TechTest.sln +++ b/TechTest/TechTest.sln @@ -5,13 +5,14 @@ VisualStudioVersion = 15.0.27004.2005 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 + ..\Solution.txt = ..\Solution.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyCompany.Tests", "AnyCompany.Tests\AnyCompany.Tests.csproj", "{1983942E-3221-4387-AE22-28B57C38ECCA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,10 +23,10 @@ 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 + {1983942E-3221-4387-AE22-28B57C38ECCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1983942E-3221-4387-AE22-28B57C38ECCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1983942E-3221-4387-AE22-28B57C38ECCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1983942E-3221-4387-AE22-28B57C38ECCA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE