From d8c4ff24be06892691b3b47abeb0ae237023e0d2 Mon Sep 17 00:00:00 2001 From: Soundararajan D Date: Sat, 30 May 2020 13:11:38 +0530 Subject: [PATCH] * Implement the CURD API's * Implement validators using Fluent Validation * Setup Dependency Injection * Implement custom logic to return location of created resource. Due to a bug with URLHelper implementation (https://github.com/dotnet/aspnetcore/issues/14877) i am not able to use URLHelper.ActionLink api to return the link of the created resource. As of now it's not 100% validated that the above mentioned issue is the reason. But for now i haven't investigated much, but made sure this work is done in a robust manner to best of the knowledge. This needs refactoring in the future. * Implement Persistence (In-Memory) using EF Core. * The project file `Hahn.ApplicationProces.May2020.Tests.csproj` is modified to include the SDK `` to enable integration testing. * Include coverlet.msbuild package to support code coverage * Create a test factory that can supply objects for both unit and integration testing * Add integration tests for Controller * Add unit tests for service and validator * Implement Equals method in `applicant.cs` to support equality comparison by properties. This is primarily used in unit testing. For now the hash code implemention is not robust but we don't have any use case to make it stronger. * Passing the options parameter to every call to ApplicantContext is not intutive. So made the options as static and passed it implicitly to the base class DbContext, while the ApplicantContext continues to be parameterless. new file: Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerIntegrationTests.cs deleted: Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerTests.cs new file: Hahn.ApplicationProces.May2020.Web.Tests/Domain/ApplicantServiceTests.cs new file: Hahn.ApplicationProces.May2020.Web.Tests/Domain/ValidatorTests.cs modified: Hahn.ApplicationProces.May2020.Web.Tests/Hahn.ApplicationProces.May2020.Tests.csproj new file: Hahn.ApplicationProces.May2020.Web.Tests/Properties/launchSettings.json deleted: Hahn.ApplicationProces.May2020.Web.Tests/Service/ApplicantControllerTests.cs new file: Hahn.ApplicationProces.May2020.Web.Tests/TestObjectFactory.cs new file: Hahn.ApplicationProces.May2020.Web.Tests/TestResults.info modified: Hahn.ApplicatonProcess.May2020.Data/Applicant.cs modified: Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs new file: Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs modified: Hahn.ApplicatonProcess.May2020.Data/Hahn.ApplicatonProcess.May2020.Data.csproj new file: Hahn.ApplicatonProcess.May2020.Data/IApplicantRepository.cs new file: Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs new file: Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs modified: Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs new file: Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs modified: Hahn.ApplicatonProcess.May2020.Domain/Hahn.ApplicatonProcess.May2020.Domain.csproj modified: Hahn.ApplicatonProcess.May2020.Domain/IApplicantService.cs new file: Hahn.ApplicatonProcess.May2020.Domain/IApplicantValidator.cs new file: Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs deleted: Hahn.ApplicatonProcess.May2020.Web/Applicant.cs modified: Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs modified: Hahn.ApplicatonProcess.May2020.Web/Hahn.ApplicatonProcess.May2020.Web.csproj modified: Hahn.ApplicatonProcess.May2020.Web/Startup.cs --- .../ApplicationControllerIntegrationTests.cs | 185 +++++++ .../Controller/ApplicationControllerTests.cs | 44 -- .../Domain/ApplicantServiceTests.cs | 156 ++++++ .../Domain/ValidatorTests.cs | 163 ++++++ ...ahn.ApplicationProces.May2020.Tests.csproj | 52 +- .../Properties/launchSettings.json | 16 + .../Service/ApplicantControllerTests.cs | 128 ----- .../TestObjectFactory.cs | 144 ++++++ .../TestResults.info | 472 ++++++++++++++++++ .../Applicant.cs | 51 +- .../ApplicantContext.cs | 11 +- .../ApplicantRepository.cs | 48 ++ ...Hahn.ApplicatonProcess.May2020.Data.csproj | 1 + .../IApplicantRepository.cs | 16 + .../ApplicantAlreadyExistsException.cs | 17 + .../ApplicantMissingExcpetion.cs | 15 + .../ApplicantService.cs | 57 ++- .../ApplicantValidator.cs | 36 ++ ...hn.ApplicatonProcess.May2020.Domain.csproj | 3 + .../IApplicantService.cs | 4 +- .../IApplicantValidator.cs | 11 + .../ValidationFailedException.cs | 18 + .../Applicant.cs | 15 - .../Controllers/ApplicantController.cs | 108 +++- .../Hahn.ApplicatonProcess.May2020.Web.csproj | 1 - Hahn.ApplicatonProcess.May2020.Web/Startup.cs | 11 +- 26 files changed, 1540 insertions(+), 243 deletions(-) create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerIntegrationTests.cs delete mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerTests.cs create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Domain/ApplicantServiceTests.cs create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Domain/ValidatorTests.cs create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Properties/launchSettings.json delete mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/Service/ApplicantControllerTests.cs create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/TestObjectFactory.cs create mode 100644 Hahn.ApplicationProces.May2020.Web.Tests/TestResults.info create mode 100644 Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Data/IApplicantRepository.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Domain/IApplicantValidator.cs create mode 100644 Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs delete mode 100644 Hahn.ApplicatonProcess.May2020.Web/Applicant.cs diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerIntegrationTests.cs b/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerIntegrationTests.cs new file mode 100644 index 0000000..fdd7912 --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerIntegrationTests.cs @@ -0,0 +1,185 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Hahn.ApplicationProces.May2020.Tests; +using Hahn.ApplicatonProcess.May2020.Data; +using Hahn.ApplicatonProcess.May2020.Domain; +using Hahn.ApplicatonProcess.May2020.Web; +using Hahn.ApplicatonProcess.May2020.Web.Controllers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Hahn.ApplicationProces.May2020.Web.Tests +{ + public class ApplicationControllerIntegrationTests : + IClassFixture>, IDisposable + { + private readonly TestObjectFactory test; + private readonly WebApplicationFactory factory; + + public ApplicationControllerIntegrationTests + (WebApplicationFactory factory) + { + test = new TestObjectFactory(); + this.factory = factory; + } + + public void Dispose() + { + test.Dispose(); + //Don't dispose WebApplicationFactory it get's injected only once + //per Test class. + } + + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Post_Returns_201_And_URL_On_Succes(string url) + { + var expected = test.applicant(); + + var content = test.request(expected); + var client = factory.CreateClient(); + + var response = await client.PostAsync(url, content); + + response.EnsureSuccessStatusCode(); + Assert.True(response.StatusCode == HttpStatusCode.Created); + } + + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Post_Returns_400_On_Validation_Failure(string url) + { + var content = test.request(test.invalidApplicant()); + var client = factory.CreateClient(); + var response = await client.PostAsync(url, content); + + Assert.True(response.StatusCode == HttpStatusCode.BadRequest); + } + + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Post_Returns_409_When_Obj_Exists(string url) + { + var content = test.request(test.applicant()); + var client = factory.CreateClient(); + var task = await client.PostAsync(url, content) + .ContinueWith(x => client.PostAsync(url, content)); + Assert.True(task.Result.StatusCode == HttpStatusCode.Conflict); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Post_Returns_400_When_Input_Is_Null(string url) + { + var applicant = test.request(null); + var client = factory.CreateClient(); + var response = await client.PostAsync(url, applicant); + + Assert.True(response.StatusCode == HttpStatusCode.BadRequest); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Get_Returns_200_On_Success(string url) + { + var expected = test.applicant(); + test.db().Add(expected); + + var client = factory.CreateClient(); + url = string.Join("/", url, expected.ID); + var response = await client.GetAsync(url); + + Assert.True(response.StatusCode == HttpStatusCode.OK); + var json = await response.Content.ReadAsStringAsync(); + var actual = test.applicant(json); + Assert.True(expected.Equals(actual)); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Get_Returns_404_When_Obj_Doesnt_Exist(string url) + { + var expected = test.applicant(); + + var client = factory.CreateClient(); + url = string.Join("/", url, expected.ID); + var response = await client.GetAsync(url); + + Assert.True(response.StatusCode == HttpStatusCode.NotFound); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Put_Returns_400_When_Input_Is_Null(string url) + { + var content = test.request(null); + var client = factory.CreateClient(); + var response = await client.PutAsync(url, content); + + Assert.True(response.StatusCode == HttpStatusCode.BadRequest); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Put_Returns_404_When_Obj_Doesnt_Exist(string url) + { + var expected = test.applicant(); + + var client = factory.CreateClient(); + var request = test.request(test.updated()); + var response = await client.PutAsync(url, request); + + Assert.True(response.StatusCode == HttpStatusCode.NotFound); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Put_Returns_400_When_Obj_Is_Invalid(string url) + { + var expected = test.applicant(); + test.db().Add(expected); + + var client = factory.CreateClient(); + var request = test.request(test.updatedInvalid()); + var response = await client.PutAsync(url, request); + + Assert.True(response.StatusCode == HttpStatusCode.BadRequest); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Delete_Returns_409_When_Obj_Doesnt_Exist(string url) + { + var client = factory.CreateClient(); + url = string.Join("/", url, test.applicant().ID); + var response = await client.DeleteAsync(url); + + Assert.True(response.StatusCode == HttpStatusCode.NotFound); + } + + [Theory] + [InlineData("/api/v1/applicant")] + public async Task Delete_Returns_200_On_Successful_Deletion(string url) + { + var expected = test.applicant(); + test.db().Add(expected); + + var client = factory.CreateClient(); + url = string.Join("/", url, expected.ID); + var response = await client.DeleteAsync(url); + + Assert.True(response.StatusCode == HttpStatusCode.OK); + } + } +} \ No newline at end of file diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerTests.cs b/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerTests.cs deleted file mode 100644 index cf6031c..0000000 --- a/Hahn.ApplicationProces.May2020.Web.Tests/Controller/ApplicationControllerTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using Hahn.ApplicatonProcess.May2020.Web.Controllers; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; - -namespace Hahn.ApplicationProces.May2020.Web.Tests -{ - public class ApplicationControllerTests - { - [Fact] - public void Test_Controller_Returns_201_And_Object_URL_On_Succesful_Creation() - { - - } - - [Fact] - public void Test_Controller_Returns_400_On_Update_With_Invalid_Object_On_Post() - { - - } - - [Fact] - public void Test_Controller_Returns_400_On_Update_With_Invalid_Object_On_Put() - { - - } - - [Fact] - public void Test_Controller_Returns_500_On_Any_Generic_Error() - { - - } - - [Fact] - public void Test_Controller_Returns_Valid_API_Versions_When_Endpoint_Doesnt_Have_Version() - { - - } - - - private ApplicantController ApplicantController() - => new ApplicantController(new NullLogger()); - } -} diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ApplicantServiceTests.cs b/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ApplicantServiceTests.cs new file mode 100644 index 0000000..e967a0d --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ApplicantServiceTests.cs @@ -0,0 +1,156 @@ +using System; +using Hahn.ApplicatonProcess.May2020.Domain; + +using Xunit; + +namespace Hahn.ApplicationProces.May2020.Tests.Domain +{ + public class ApplicantServiceTests : IDisposable + { + private readonly TestObjectFactory test; + public ApplicantServiceTests() + { + test = new TestObjectFactory(); + } + + public void Dispose() + { + test.Dispose(); + } + + [Fact] + public void Create_Throws_Validation_Failed_Exception_On_Null() + { + var service = test.service(); + Assert.Throws(() => + { + service.Create(null); + }); + + } + + [Fact] + public void Create_Throws_Validation_Failed_Exception_On_Invalid_Obj() + { + var service = test.service(); + Assert.Throws(() => + { + service.Create(test.invalidApplicant()); + }); + } + + [Fact] + public void + Create_Throws_Applicant_Already_Exists_Exception_On_Duplicate() + { + test.db().Add(test.applicant()); + var service = test.service(); + Assert.Throws(() => + { + service.Create(test.applicant()); + }); + } + + [Fact] + public void Create_Suffesfully_Creates_Application() + + { + var expected = test.applicant(); + var service = test.service(); + service.Create(expected); + + Assert.True(test.db().Get(expected.ID) != null); + } + + [Fact] + public void + Delete_Throws_Applicant_Missing_Exception_When_Object_Not_Found() + { + var expected = test.applicant(); + test.db().Add(expected); + Assert.Throws(() => + { + test.service().Delete(expected.ID + 1); + }); + } + + [Fact] + public void Delete_Deletes_The_Object_Succesfully_If_Exists() + { + var expected = test.applicant(); + test.db().Add(expected); + test.service().Delete(expected.ID); + + Assert.True(test.db().Get(expected.ID) == null); + } + + [Fact] + public void Get_Throws_Applicant_Missing_Exception_When_Obj_Not_Found() + { + var expected = test.applicant(); + Assert.Throws(() => + { + test.service().Get(expected.ID); + }); + } + + [Fact] + public void Get_Returns_Value_succesfully_when_exists() + { + var expected = test.applicant(); + test.db().Add(expected); + var actual = test.service().Get(expected.ID); + Assert.True(expected.Equals(actual)); + } + + [Fact] + public void Update_Updates_Value_succesfully_when_exists() + { + var expected = test.applicant(); + test.db().Add(expected); + var updated = test.updated(); + test.service().Update(updated); + + Assert.False(test.db().Get(expected.ID).Equals(expected)); + Assert.True(test.db().Get(expected.ID).Equals(test.updated())); + } + + [Fact] + public void Update_Throws_Argument_Null_Exception_On_Null_Input() + { + Assert.Throws(() => + { + test.service().Update(null); + }); + } + + [Fact] + public void Update_Throws_Applicant_Missing_Exception_When_Not_Found() + { + Assert.Throws(() => + { + test.service().Update(test.applicant()); + }); + } + + [Fact] + public void Update_Throws_Validation_Failed_Exception_When_Invalid() + { + test.db().Add(test.applicant()); + Assert.Throws(() => + { + test.service().Update(test.invalidApplicant()); + }); + } + + [Fact] + public void Update_Updates_Succesfully_On_Valid_Input() + { + var expected = test.applicant(); + test.db().Add(expected); + test.service().Update(test.updated()); + + Assert.True(test.db().Get(expected.ID).Equals(test.updated())); + } + } +} diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ValidatorTests.cs b/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ValidatorTests.cs new file mode 100644 index 0000000..e639d5b --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/Domain/ValidatorTests.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Xunit; + +namespace Hahn.ApplicationProces.May2020.Tests.Domain +{ + public class ValidatorTests : IDisposable + { + private readonly TestObjectFactory test; + public ValidatorTests() + { + test = new TestObjectFactory(); + } + + public void Dispose() + { + test.Dispose(); + } + + [Fact] + public void Validation_Fails_For_Null_Input() + { + var v = test.validator(); + Assert.Throws(() => + { + v.Validate(null, out _); + }); + } + + [Theory] + [InlineData("a")] + [InlineData("")] + public void Validation_Fails_For_Invalid_Name(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Name = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.False(valid); + Assert.NotEmpty(errors); + + } + + [Theory] + [InlineData("Soundararajan Dhakshinamoorthy")] + public void Validation_Passes_For_Valid_Name(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Name = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.True(valid); + Assert.Empty(errors); + } + + [Theory] + [InlineData("a")] + [InlineData("")] + public void Validation_Fails_For_Invalid_FamilyName(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.FamilyName = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.False(valid); + Assert.NotEmpty(errors); + + } + + [Theory] + [InlineData("Soundararajan Dhakshinamoorthy")] + public void Validation_Passes_For_Valid_FamilyName(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.FamilyName = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.True(valid); + Assert.Empty(errors); + } + + [Theory] + [InlineData("a")] + [InlineData("")] + public void Validation_Fails_For_Invalid_Address(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Address = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.False(valid); + Assert.NotEmpty(errors); + } + + [Theory] + [InlineData("Dusselorf, Germany, Europe")] + public void Validation_Passes_For_Valid_Address(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Address = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.True(valid); + Assert.Empty(errors); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(100)] + [InlineData(-10)] + public void Validation_Fails_For_Invalid_Age(int value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Age = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.False(valid); + Assert.NotEmpty(errors); + } + + [Theory] + [InlineData(20)] + [InlineData(60)] + public void Validation_Passes_For_Valid_Age(int value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.Age = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.True(valid); + Assert.Empty(errors); + } + + [Theory] + [InlineData("soundararajanoutlook.com")] + [InlineData("@soundararrajan.outlook.com")] + [InlineData("soundararrajan.outlook.com@")] + public void Validation_Fails_For_Invalid_Email(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.EMailAdress = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.False(valid); + Assert.NotEmpty(errors); + } + + [Theory] + [InlineData("soundararajan@outlook.com")] + [InlineData("er.soundararajan@hotmail.com")] + public void Validation_Passes_For_Valid_Email(string value) + { + var v = test.validator(); + var entity = test.applicant(); + entity.EMailAdress = value; + var valid = v.Validate(entity, out IEnumerable errors); + Assert.True(valid); + Assert.Empty(errors); + } + } +} diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Hahn.ApplicationProces.May2020.Tests.csproj b/Hahn.ApplicationProces.May2020.Web.Tests/Hahn.ApplicationProces.May2020.Tests.csproj index b1a9a77..931bc4e 100644 --- a/Hahn.ApplicationProces.May2020.Web.Tests/Hahn.ApplicationProces.May2020.Tests.csproj +++ b/Hahn.ApplicationProces.May2020.Web.Tests/Hahn.ApplicationProces.May2020.Tests.csproj @@ -1,25 +1,27 @@ - - - - netcoreapp3.1 - - false - - - - - - - - - - - - - - - - - - - + + + + netcoreapp3.1 + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + \ No newline at end of file diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Properties/launchSettings.json b/Hahn.ApplicationProces.May2020.Web.Tests/Properties/launchSettings.json new file mode 100644 index 0000000..f28be4c --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true + }, + "profiles": { + "Hahn.ApplicationProces.May2020.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:34246" + } + } +} \ No newline at end of file diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/Service/ApplicantControllerTests.cs b/Hahn.ApplicationProces.May2020.Web.Tests/Service/ApplicantControllerTests.cs deleted file mode 100644 index 48463c8..0000000 --- a/Hahn.ApplicationProces.May2020.Web.Tests/Service/ApplicantControllerTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using Hahn.ApplicatonProcess.May2020.Data; -using Hahn.ApplicatonProcess.May2020.Domain; -using Hahn.ApplicatonProcess.May2020.Web.Controllers; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; - -namespace Hahn.ApplicationProces.May2020.Web.Tests -{ - public class ApplicantServiceTests - { - private readonly DbContext context; - public ApplicantServiceTests() - { - context = new ApplicantContext(options()); - } - - [Fact] - public void Test_Get_Returns_Valid_Applicant_For_Given_Id() - { - - } - - [Fact] - public void Test_Name_Is_Atleast_Five_Characters_Validation_Runs() - { - - } - - [Fact] - public void Test_FamilyName_Is_Atleast_Five_Characters_Validation_Runs() - { - - } - - [Fact] - public void Test_Address_Atleast_Ten_Characters_Validation_Runs() - { - - } - - [Fact] - public void Test_Country_Name_Is_Valid_Validation_Runs() - { - - } - - [Fact] - public void Test_Email_Address_Validation_Runs() - { - - } - - [Fact] - public void Test_Age_Is_Between_20_And_60_Validation_Runs() - { - - } - - [Fact] - public void Test_Hired_Is_Not_Null_When_Provided_Validation_Runs() - { - - } - - [Fact] - public void Test_Get_Throws_Exception_When_Applicant_Not_Found() - { - - } - - [Fact] - public void Test_Create_Creates_Applicant_Without_Issues() - { - - } - - [Fact] - public void Test_Update_Returns_False_When_Applicant_Not_Present() - { - - } - - [Fact] - public void Test_Update_Updates_Applicant_Without_Issues() - { - - } - - [Fact] - public void Test_Delete_Deletes_Applicant_Without_Issues_When_Present() - { - - } - - [Fact] - public void Test_Delete_Returns_FalseWhen_Applicant_Not_Present() - { - - } - - - private Applicant Applicant() - { - return new Applicant() - { - ID = 100, - Name = "Soundararajan", - FamilyName = "Dhakshinamoorthy", - Address = "47, Jones Cassia, Karanai Main Road, Ottiaymbakkam", - Age = 35, - CountryOfOrigin = "India", - EMailAdress = "soundararajan@outlook.com", - Hired = default(bool) - }; - } - - private IApplicantService Service() - => new ApplicantService(); - private DbContextOptions options() - { - return new DbContextOptionsBuilder(). - UseInMemoryDatabase("Applicant"). - Options; - } - } -} diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/TestObjectFactory.cs b/Hahn.ApplicationProces.May2020.Web.Tests/TestObjectFactory.cs new file mode 100644 index 0000000..28c592c --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/TestObjectFactory.cs @@ -0,0 +1,144 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using Hahn.ApplicatonProcess.May2020.Data; +using Hahn.ApplicatonProcess.May2020.Domain; +using Hahn.ApplicatonProcess.May2020.Web.Controllers; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Hahn.ApplicationProces.May2020.Tests +{ + internal class TestObjectFactory : IDisposable + { + /// + /// ran for each test + /// + internal TestObjectFactory() + { + //Though we create a new ApplicationContext every time + //it doesn't get created every time. It is initialized + //once for an AppDomain. But we manually Ensure + //Creation in coustructor, because we manually dispose + //it for every test run. + var context = new ApplicantContext(); + context.Database.EnsureCreated(); + } + + /// + /// Ran after each test. + /// + public void Dispose() + { + //the database need to be cleaned up post every run + //to cleanup the data setup for the previous test run + var context = new ApplicantContext(); + context.Database.EnsureDeleted(); + } + + internal ILogger logger() + { + return new NullLogger(); + } + + internal IApplicantService service() + { + return new ApplicantService(validator(), db()); + } + + internal IApplicantRepository db() + { + return new ApplicantRepository(); + } + + internal ApplicantController controller() + { + var c = new ApplicantController(logger(), service()); + return c; + } + + internal IApplicantValidator validator() + { + return new ApplicantValidator(); + } + + internal ByteArrayContent request(Applicant a) + { + var bytes = Encoding.UTF8.GetBytes(applicantJson(a)); + var content = new ByteArrayContent(bytes); + content.Headers.ContentType = + new MediaTypeHeaderValue("application/json"); + return content; + } + + internal string applicantJson(Applicant a) + { + return JsonSerializer.Serialize(a, typeof(Applicant)); + } + + internal string applicantJson() + { + return JsonSerializer.Serialize(applicant(), typeof(Applicant)); + } + + internal Applicant invalidApplicant() + { + return new Applicant() + { + ID = 100, + Name = "a", + FamilyName = "b", + Address = "find", + EMailAdress = "duck", + Age = 1, + CountryOfOrigin = "India", + Hired = false + }; + } + + + internal Applicant applicant() + { + return new Applicant() + { + ID = 100, + Name = "Soundararajan", + FamilyName = "Dhakshinamoorthy", + Address = "47, Jones Cassia, Karanai Main Road, Ottiaymbakkam", + Age = 35, + CountryOfOrigin = "India", + EMailAdress = "soundararajan@outlook.com", + Hired = default(bool) + }; + } + + internal Applicant updated() + { + var a = applicant(); + a.Address = $"updated_address"; + a.Name = $"updated_name"; + return a; + } + + internal Applicant updatedInvalid() + { + var a = applicant(); + a.Address = string.Empty; + return a; + } + + internal Applicant applicant(string json) + { + //The deserialization of the json from HttpResponse fails + //if the naming policy is not set explicitly. + var options = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + var obj = JsonSerializer.Deserialize(json, options); + return obj; + } + } +} diff --git a/Hahn.ApplicationProces.May2020.Web.Tests/TestResults.info b/Hahn.ApplicationProces.May2020.Web.Tests/TestResults.info new file mode 100644 index 0000000..fd1fae6 --- /dev/null +++ b/Hahn.ApplicationProces.May2020.Web.Tests/TestResults.info @@ -0,0 +1,472 @@ +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs +FN:14,System.String Hahn.ApplicatonProcess.May2020.Domain.ApplicantAlreadyExistsException::get_Message() +FNDA:1,System.String Hahn.ApplicatonProcess.May2020.Domain.ApplicantAlreadyExistsException::get_Message() +DA:15,1 +FN:8,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantAlreadyExistsException::.ctor(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:2,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantAlreadyExistsException::.ctor(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:9,2 +DA:10,2 +DA:11,2 +DA:12,2 +LF:5 +LH:5 +BRF:0 +BRH:0 +FNF:2 +FNH:2 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs +FN:12,System.String Hahn.ApplicatonProcess.May2020.Domain.ApplicantMissingExcpetion::get_Message() +FNDA:3,System.String Hahn.ApplicatonProcess.May2020.Domain.ApplicantMissingExcpetion::get_Message() +DA:13,3 +FN:6,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantMissingExcpetion::.ctor(System.Int32) +FNDA:6,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantMissingExcpetion::.ctor(System.Int32) +DA:7,6 +DA:8,6 +DA:9,6 +DA:10,6 +LF:5 +LH:5 +BRF:0 +BRH:0 +FNF:2 +FNH:2 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs +FN:19,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Create(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:8,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Create(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:20,8 +DA:21,8 +DA:22,1 +DA:23,1 +DA:25,7 +DA:26,2 +DA:28,5 +DA:30,5 +DA:31,2 +DA:33,3 +DA:35,3 +BRDA:21,7,0,1 +BRDA:21,7,1,7 +BRDA:25,48,0,2 +BRDA:25,48,1,5 +BRDA:30,83,0,2 +BRDA:30,83,1,3 +FN:37,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Delete(System.Int32) +FNDA:4,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Delete(System.Int32) +DA:38,4 +DA:39,4 +DA:41,4 +DA:42,2 +DA:44,2 +DA:45,2 +BRDA:41,20,0,2 +BRDA:41,20,1,2 +FN:47,Hahn.ApplicatonProcess.May2020.Data.Applicant Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Get(System.Int32) +FNDA:4,Hahn.ApplicatonProcess.May2020.Data.Applicant Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Get(System.Int32) +DA:48,4 +DA:49,4 +DA:51,4 +DA:52,2 +DA:54,2 +DA:55,2 +BRDA:51,20,0,2 +BRDA:51,20,1,2 +FN:57,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Update(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:7,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::Update(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:58,7 +DA:59,7 +DA:60,1 +DA:62,6 +DA:64,6 +DA:65,2 +DA:67,4 +DA:68,2 +DA:70,2 +DA:71,2 +BRDA:59,7,0,1 +BRDA:59,7,1,6 +BRDA:64,44,0,2 +BRDA:64,44,1,4 +BRDA:67,79,0,2 +BRDA:67,79,1,2 +FN:11,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::.ctor(Hahn.ApplicatonProcess.May2020.Domain.IApplicantValidator,Hahn.ApplicatonProcess.May2020.Data.IApplicantRepository) +FNDA:25,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantService::.ctor(Hahn.ApplicatonProcess.May2020.Domain.IApplicantValidator,Hahn.ApplicatonProcess.May2020.Data.IApplicantRepository) +DA:12,25 +DA:13,25 +DA:14,25 +DA:15,25 +DA:16,25 +DA:17,25 +LF:39 +LH:39 +BRF:16 +BRH:16 +FNF:5 +FNH:5 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs +FN:25,System.Boolean Hahn.ApplicatonProcess.May2020.Domain.ApplicantValidator::Validate(Hahn.ApplicatonProcess.May2020.Data.Applicant,System.Collections.Generic.IEnumerable`1&) +FNDA:32,System.Boolean Hahn.ApplicatonProcess.May2020.Domain.ApplicantValidator::Validate(Hahn.ApplicatonProcess.May2020.Data.Applicant,System.Collections.Generic.IEnumerable`1&) +DA:26,32 +DA:27,32 +DA:28,1 +DA:29,1 +DA:31,31 +DA:32,44 +DA:33,31 +DA:34,31 +BRDA:27,7,0,1 +BRDA:27,7,1,31 +FN:12,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantValidator::.ctor() +FNDA:46,System.Void Hahn.ApplicatonProcess.May2020.Domain.ApplicantValidator::.ctor() +DA:13,46 +DA:14,46 +DA:15,46 +DA:16,46 +DA:17,46 +DA:18,46 +DA:19,46 +DA:20,46 +DA:21,46 +DA:22,46 +LF:18 +LH:18 +BRF:2 +BRH:2 +FNF:2 +FNH:2 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs +FN:15,System.String Hahn.ApplicatonProcess.May2020.Domain.ValidationFailedException::get_Message() +FNDA:2,System.String Hahn.ApplicatonProcess.May2020.Domain.ValidationFailedException::get_Message() +DA:16,2 +FN:9,System.Void Hahn.ApplicatonProcess.May2020.Domain.ValidationFailedException::.ctor(System.Collections.Generic.IEnumerable`1) +FNDA:5,System.Void Hahn.ApplicatonProcess.May2020.Domain.ValidationFailedException::.ctor(System.Collections.Generic.IEnumerable`1) +DA:10,5 +DA:11,5 +DA:12,5 +DA:13,5 +LF:5 +LH:5 +BRF:0 +BRH:0 +FNF:2 +FNH:2 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Data/Applicant.cs +FN:8,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::get_ID() +FNDA:102,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::get_ID() +DA:9,102 +FN:9,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Name() +FNDA:112,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Name() +DA:10,112 +FN:10,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_FamilyName() +FNDA:105,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_FamilyName() +DA:11,105 +FN:11,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Address() +FNDA:111,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Address() +DA:12,111 +FN:12,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_CountryOfOrigin() +FNDA:71,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_CountryOfOrigin() +DA:13,71 +FN:13,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_EMailAdress() +FNDA:107,System.String Hahn.ApplicatonProcess.May2020.Data.Applicant::get_EMailAdress() +DA:14,107 +FN:14,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Age() +FNDA:108,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Age() +DA:15,108 +FN:15,System.Boolean Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Hired() +FNDA:102,System.Boolean Hahn.ApplicatonProcess.May2020.Data.Applicant::get_Hired() +DA:16,102 +FN:18,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::GetHashCode() +FNDA:0,System.Int32 Hahn.ApplicatonProcess.May2020.Data.Applicant::GetHashCode() +DA:19,0 +DA:20,0 +DA:21,0 +FN:23,System.Boolean Hahn.ApplicatonProcess.May2020.Data.Applicant::Equals(System.Object) +FNDA:5,System.Boolean Hahn.ApplicatonProcess.May2020.Data.Applicant::Equals(System.Object) +DA:24,5 +DA:25,5 +DA:26,0 +DA:28,5 +DA:29,0 +DA:31,5 +DA:32,0 +DA:34,5 +DA:36,5 +DA:37,0 +DA:39,5 +DA:40,1 +DA:42,4 +DA:43,0 +DA:45,4 +DA:46,0 +DA:48,4 +DA:49,0 +DA:51,4 +DA:52,0 +DA:54,4 +DA:55,0 +DA:57,4 +DA:58,0 +DA:60,4 +DA:61,5 +BRDA:25,7,0,0 +BRDA:25,7,1,5 +BRDA:28,30,0,0 +BRDA:28,30,1,5 +BRDA:31,47,0,0 +BRDA:31,47,1,5 +BRDA:36,84,0,0 +BRDA:36,84,1,5 +BRDA:39,114,0,1 +BRDA:39,114,1,4 +BRDA:42,144,0,0 +BRDA:42,144,1,4 +BRDA:45,174,0,0 +BRDA:45,174,1,4 +BRDA:48,201,0,0 +BRDA:48,201,1,4 +BRDA:51,228,0,0 +BRDA:51,228,1,4 +BRDA:54,255,0,0 +BRDA:54,255,1,4 +BRDA:57,282,0,0 +BRDA:57,282,1,4 +LF:37 +LH:24 +BRF:22 +BRH:12 +FNF:10 +FNH:9 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs +FN:21,Microsoft.EntityFrameworkCore.DbSet`1 Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::get_Applicants() +FNDA:168,Microsoft.EntityFrameworkCore.DbSet`1 Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::get_Applicants() +DA:22,168 +FN:11,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::.cctor() +FNDA:1,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::.cctor() +DA:12,1 +DA:13,1 +DA:14,1 +DA:15,1 +DA:16,1 +FN:17,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::.ctor() +FNDA:131,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantContext::.ctor() +DA:18,131 +DA:19,131 +DA:20,131 +LF:9 +LH:9 +BRF:0 +BRH:0 +FNF:3 +FNH:3 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs +FN:13,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Add(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:13,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Add(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:14,13 +DA:15,13 +DA:16,13 +DA:17,13 +DA:18,13 +DA:19,13 +DA:20,13 +FN:22,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Remove(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:2,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Remove(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:23,2 +DA:24,2 +DA:25,2 +DA:26,2 +DA:27,2 +DA:28,2 +DA:29,2 +FN:31,Hahn.ApplicatonProcess.May2020.Data.Applicant Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Get(System.Int32) +FNDA:24,Hahn.ApplicatonProcess.May2020.Data.Applicant Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Get(System.Int32) +DA:32,24 +DA:33,24 +DA:34,24 +DA:35,24 +DA:37,24 +FN:39,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Update(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:2,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::Update(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:40,2 +DA:41,2 +DA:42,2 +DA:43,2 +DA:44,2 +DA:45,2 +DA:46,2 +FN:8,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::.ctor() +FNDA:40,System.Void Hahn.ApplicatonProcess.May2020.Data.ApplicantRepository::.ctor() +DA:9,40 +DA:10,40 +DA:11,40 +LF:29 +LH:29 +BRF:0 +BRH:0 +FNF:5 +FNH:5 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Web/Program.cs +FN:14,System.Void Hahn.ApplicatonProcess.May2020.Web.Program::Main(System.String[]) +FNDA:0,System.Void Hahn.ApplicatonProcess.May2020.Web.Program::Main(System.String[]) +DA:15,0 +DA:16,0 +DA:17,0 +FN:19,Microsoft.Extensions.Hosting.IHostBuilder Hahn.ApplicatonProcess.May2020.Web.Program::CreateHostBuilder(System.String[]) +FNDA:1,Microsoft.Extensions.Hosting.IHostBuilder Hahn.ApplicatonProcess.May2020.Web.Program::CreateHostBuilder(System.String[]) +DA:20,1 +DA:21,1 +DA:22,2 +DA:23,2 +DA:24,2 +LF:8 +LH:5 +BRF:0 +BRH:0 +FNF:2 +FNH:1 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Web/Startup.cs +FN:25,Microsoft.Extensions.Configuration.IConfiguration Hahn.ApplicatonProcess.May2020.Web.Startup::get_Configuration() +FNDA:0,Microsoft.Extensions.Configuration.IConfiguration Hahn.ApplicatonProcess.May2020.Web.Startup::get_Configuration() +DA:26,0 +FN:29,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection) +FNDA:1,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection) +DA:30,1 +DA:31,1 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,1 +DA:39,1 +DA:40,2 +DA:41,2 +DA:42,2 +DA:44,1 +FN:47,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Hosting.IWebHostEnvironment) +FNDA:1,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Hosting.IWebHostEnvironment) +DA:48,1 +DA:49,1 +DA:50,1 +DA:51,1 +DA:52,1 +DA:54,1 +DA:56,1 +DA:58,1 +DA:60,1 +DA:61,2 +DA:62,2 +DA:63,2 +DA:64,1 +BRDA:49,9,0,1 +BRDA:49,9,1,1 +FN:20,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::.ctor(Microsoft.Extensions.Configuration.IConfiguration) +FNDA:1,System.Void Hahn.ApplicatonProcess.May2020.Web.Startup::.ctor(Microsoft.Extensions.Configuration.IConfiguration) +DA:21,1 +DA:22,1 +DA:23,1 +DA:24,1 +LF:30 +LH:29 +BRF:2 +BRH:2 +FNF:4 +FNH:3 +end_of_record +SF:/Users/sdhaksh5/Projects/Hahn.ApplicatonProcess.Application/Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs +FN:30,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Get(System.Int32) +FNDA:2,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Get(System.Int32) +DA:31,2 +DA:33,2 +DA:34,2 +DA:35,1 +DA:37,1 +DA:38,1 +DA:39,1 +DA:41,2 +FN:49,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Post(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:4,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Post(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:50,4 +DA:52,4 +DA:53,4 +DA:54,2 +DA:55,2 +DA:56,2 +DA:58,1 +DA:59,1 +DA:60,1 +DA:63,1 +DA:64,1 +DA:65,1 +DA:67,0 +DA:68,0 +DA:69,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,4 +FN:78,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Put(Hahn.ApplicatonProcess.May2020.Data.Applicant) +FNDA:2,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Put(Hahn.ApplicatonProcess.May2020.Data.Applicant) +DA:79,2 +DA:81,2 +DA:82,2 +DA:83,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:90,1 +DA:91,1 +DA:92,1 +DA:94,1 +DA:95,1 +DA:96,1 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:103,2 +FN:106,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Delete(System.Int32) +FNDA:2,Microsoft.AspNetCore.Mvc.IActionResult Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::Delete(System.Int32) +DA:107,2 +DA:109,2 +DA:110,2 +DA:111,1 +DA:113,1 +DA:114,1 +DA:115,1 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:122,2 +FN:124,System.String Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::ComposeUrl(System.Int32) +FNDA:2,System.String Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::ComposeUrl(System.Int32) +DA:125,2 +DA:132,2 +DA:133,0 +DA:134,0 +DA:136,2 +DA:137,2 +DA:138,2 +BRDA:132,12,0,0 +BRDA:132,12,1,2 +FN:20,System.Void Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::.ctor(Microsoft.Extensions.Logging.ILogger`1,Hahn.ApplicatonProcess.May2020.Domain.IApplicantService) +FNDA:12,System.Void Hahn.ApplicatonProcess.May2020.Web.Controllers.ApplicantController::.ctor(Microsoft.Extensions.Logging.ILogger`1,Hahn.ApplicatonProcess.May2020.Domain.IApplicantService) +DA:21,12 +DA:22,12 +DA:23,12 +DA:24,12 +DA:25,12 +LF:69 +LH:49 +BRF:2 +BRH:1 +FNF:6 +FNH:6 +end_of_record \ No newline at end of file diff --git a/Hahn.ApplicatonProcess.May2020.Data/Applicant.cs b/Hahn.ApplicatonProcess.May2020.Data/Applicant.cs index 47b9884..dcfe338 100644 --- a/Hahn.ApplicatonProcess.May2020.Data/Applicant.cs +++ b/Hahn.ApplicatonProcess.May2020.Data/Applicant.cs @@ -4,7 +4,7 @@ namespace Hahn.ApplicatonProcess.May2020.Data { - public sealed class Applicant + public sealed class Applicant { public int ID { get; set; } public string Name { get; set; } @@ -12,7 +12,52 @@ public sealed class Applicant public string Address { get; set; } public string CountryOfOrigin { get; set; } public string EMailAdress { get; set; } - public uint Age { get; set; } - public bool Hired { get; set; } = false; + public int Age { get; set; } + public bool Hired { get; set; } + + public override int GetHashCode() + { + return ID; + } + + public override bool Equals(object obj) + { + if (obj == null) + return false; + + if (!(obj is Applicant)) + return false; + + if (object.ReferenceEquals(obj, this)) + return true; + + var other = obj as Applicant; + + if (this.ID != other.ID) + return false; + + if (this.Name != other.Name) + return false; + + if (this.FamilyName != other.FamilyName) + return false; + + if (this.Address != other.Address) + return false; + + if (this.CountryOfOrigin != other.CountryOfOrigin) + return false; + + if (this.EMailAdress != other.EMailAdress) + return false; + + if (this.Age != other.Age) + return false; + + if (this.Hired != other.Hired) + return false; + + return true; + } } } diff --git a/Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs b/Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs index e45d458..2b16573 100644 --- a/Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs +++ b/Hahn.ApplicatonProcess.May2020.Data/ApplicantContext.cs @@ -6,13 +6,16 @@ namespace Hahn.ApplicatonProcess.May2020.Data { public class ApplicantContext : DbContext { - public ApplicantContext(DbContextOptions options) - : base(options) + //By default we will use InMemoryDatabase, so we wiil + static DbContextOptions options; + static ApplicantContext() { + var builder = new DbContextOptionsBuilder(); + builder.UseInMemoryDatabase(nameof(Applicant)); + options = builder.Options; } - public ApplicantContext() - : base() + : base(options) { } diff --git a/Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs b/Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs new file mode 100644 index 0000000..00eb380 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Data/ApplicantRepository.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Hahn.ApplicatonProcess.May2020.Data; + +namespace Hahn.ApplicatonProcess.May2020.Data +{ + public sealed class ApplicantRepository : IApplicantRepository + { + public ApplicantRepository() + { + } + + public void Add(Applicant applicant) + { + using (var db = new ApplicantContext()) + { + db.Applicants.Add(applicant); + db.SaveChanges(); + } + } + + public void Remove(Applicant applicant) + { + using (var db = new ApplicantContext()) + { + db.Remove(applicant); + db.SaveChanges(); + } + } + + public Applicant Get(int id) + { + using (var db = new ApplicantContext()) + { + return db.Applicants.FirstOrDefault(x => x.ID == id); + } + } + + public void Update(Applicant applicant) + { + using (var db = new ApplicantContext()) + { + db.Update(applicant); + db.SaveChanges(); + } + } + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Data/Hahn.ApplicatonProcess.May2020.Data.csproj b/Hahn.ApplicatonProcess.May2020.Data/Hahn.ApplicatonProcess.May2020.Data.csproj index 5c88a55..ed27b32 100644 --- a/Hahn.ApplicatonProcess.May2020.Data/Hahn.ApplicatonProcess.May2020.Data.csproj +++ b/Hahn.ApplicatonProcess.May2020.Data/Hahn.ApplicatonProcess.May2020.Data.csproj @@ -5,6 +5,7 @@ + diff --git a/Hahn.ApplicatonProcess.May2020.Data/IApplicantRepository.cs b/Hahn.ApplicatonProcess.May2020.Data/IApplicantRepository.cs new file mode 100644 index 0000000..ac29de1 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Data/IApplicantRepository.cs @@ -0,0 +1,16 @@ +using System; +using Hahn.ApplicatonProcess.May2020.Data; + +namespace Hahn.ApplicatonProcess.May2020.Data +{ + public interface IApplicantRepository + { + void Add(Applicant applicant); + + void Remove(Applicant applicant); + + Applicant Get(int id); + + void Update(Applicant applicant); + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs new file mode 100644 index 0000000..dff1f97 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantAlreadyExistsException.cs @@ -0,0 +1,17 @@ +using System; +using Hahn.ApplicatonProcess.May2020.Data; + +namespace Hahn.ApplicatonProcess.May2020.Domain +{ + public sealed class ApplicantAlreadyExistsException : Exception + { + private readonly Applicant applicant; + public ApplicantAlreadyExistsException(Applicant applicant) + { + this.applicant = applicant; + } + + public override string Message => + $"The applicant with {applicant.ID} already exists"; + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs new file mode 100644 index 0000000..b6b58a8 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantMissingExcpetion.cs @@ -0,0 +1,15 @@ +using System; +namespace Hahn.ApplicatonProcess.May2020.Domain +{ + public class ApplicantMissingExcpetion : Exception + { + private readonly int id; + public ApplicantMissingExcpetion(int ID) + { + this.id = ID; + } + + public override string Message => + $"The applicant with ID {id} does not exist"; + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs index a6869c7..dba78ef 100644 --- a/Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs +++ b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantService.cs @@ -1,28 +1,73 @@ using System; +using System.Linq; +using System.Collections.Generic; using Hahn.ApplicatonProcess.May2020.Data; namespace Hahn.ApplicatonProcess.May2020.Domain { public sealed class ApplicantService : IApplicantService { + private readonly IApplicantValidator validator; + private readonly IApplicantRepository repo; + public ApplicantService(IApplicantValidator validator, + IApplicantRepository repo) + { + this.validator = validator; + this.repo = repo; + } + public void Create(Applicant applicant) { - throw new NotImplementedException(); + if (applicant == null) + throw new ValidationFailedException + (new[] { $"{nameof(applicant)} is null or empty" }); + + if (!validator.Validate(applicant, out IEnumerable errors)) + throw new ValidationFailedException(errors); + + var present = repo.Get(applicant.ID) != null; + + if (present) + throw new ApplicantAlreadyExistsException(applicant); + + repo.Add(applicant); + } - public bool Delete(int ID) + public void Delete(int ID) { - throw new NotImplementedException(); + var applicant = repo.Get(ID); + + if (applicant == null) + throw new ApplicantMissingExcpetion(ID); + + repo.Remove(applicant); } public Applicant Get(int ID) { - throw new NotImplementedException(); + var applicant = repo.Get(ID); + + if (applicant == null) + throw new ApplicantMissingExcpetion(ID); + + return applicant; } - public bool Update(int ID, Applicant applicant) + public void Update(Applicant dest) { - throw new NotImplementedException(); + if (dest == null) + throw new ArgumentNullException(nameof(dest)); + + var source = repo.Get(dest.ID); + + if (source == null) + throw new ApplicantMissingExcpetion(dest.ID); + + if (!validator.Validate(dest, out IEnumerable errors)) + throw new ValidationFailedException(errors); + + repo.Update(dest); } } } diff --git a/Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs new file mode 100644 index 0000000..6925da5 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Domain/ApplicantValidator.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentValidation; +using FluentValidation.Validators; +using Hahn.ApplicatonProcess.May2020.Data; + +namespace Hahn.ApplicatonProcess.May2020.Domain +{ + public class ApplicantValidator : AbstractValidator, + IApplicantValidator + { + public ApplicantValidator() + { + RuleFor(x => x.Name).MinimumLength(5); + RuleFor(x => x.FamilyName).MinimumLength(5); + RuleFor(x => x.Address).MinimumLength(10); + RuleFor(x => x.Age).InclusiveBetween(20, 60); + RuleFor(x => x.Hired).NotNull(); + RuleFor(x => x.EMailAdress). + EmailAddress(EmailValidationMode.AspNetCoreCompatible); + } + + public bool Validate(Applicant applicant, + out IEnumerable errors) + { + if (applicant == null) + throw new ArgumentNullException( + $"{nameof(applicant)} cannot be null"); + + var result = this.Validate(applicant); + errors = result.Errors.Select(x => x.ErrorMessage); + return result.IsValid; + } + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Domain/Hahn.ApplicatonProcess.May2020.Domain.csproj b/Hahn.ApplicatonProcess.May2020.Domain/Hahn.ApplicatonProcess.May2020.Domain.csproj index 13ab002..2abdb80 100644 --- a/Hahn.ApplicatonProcess.May2020.Domain/Hahn.ApplicatonProcess.May2020.Domain.csproj +++ b/Hahn.ApplicatonProcess.May2020.Domain/Hahn.ApplicatonProcess.May2020.Domain.csproj @@ -7,4 +7,7 @@ + + + diff --git a/Hahn.ApplicatonProcess.May2020.Domain/IApplicantService.cs b/Hahn.ApplicatonProcess.May2020.Domain/IApplicantService.cs index 6806cd3..5907595 100644 --- a/Hahn.ApplicatonProcess.May2020.Domain/IApplicantService.cs +++ b/Hahn.ApplicatonProcess.May2020.Domain/IApplicantService.cs @@ -18,14 +18,14 @@ public interface IApplicantService /// A valid ID of an applicant to delete. /// True if deleted successfully. False otherwise /// Throws excpetion if the ID exists but was not deleted due to application error - bool Delete(int ID); + void Delete(int ID); /// /// Updates an applicant with the given ID /// /// /// - bool Update(int ID, Applicant applicant); + void Update(Applicant applicant); /// /// Retrieves the applicant with the given ID. diff --git a/Hahn.ApplicatonProcess.May2020.Domain/IApplicantValidator.cs b/Hahn.ApplicatonProcess.May2020.Domain/IApplicantValidator.cs new file mode 100644 index 0000000..cf0d3e8 --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Domain/IApplicantValidator.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using Hahn.ApplicatonProcess.May2020.Data; + +namespace Hahn.ApplicatonProcess.May2020.Domain +{ + public interface IApplicantValidator + { + bool Validate(Applicant applicant, out IEnumerable errors); + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs b/Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs new file mode 100644 index 0000000..fd2dc0f --- /dev/null +++ b/Hahn.ApplicatonProcess.May2020.Domain/ValidationFailedException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Hahn.ApplicatonProcess.May2020.Domain +{ + public sealed class ValidationFailedException : Exception + { + private readonly IEnumerable errors; + + public ValidationFailedException(IEnumerable errors) + { + this.errors = errors; + } + + public override string Message => + string.Join(Environment.NewLine, "Validation failed : ", errors); + } +} diff --git a/Hahn.ApplicatonProcess.May2020.Web/Applicant.cs b/Hahn.ApplicatonProcess.May2020.Web/Applicant.cs deleted file mode 100644 index f18e778..0000000 --- a/Hahn.ApplicatonProcess.May2020.Web/Applicant.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Hahn.ApplicatonProcess.May2020.Web -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} diff --git a/Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs b/Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs index 60c4e71..203f5f3 100644 --- a/Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs +++ b/Hahn.ApplicatonProcess.May2020.Web/Controllers/ApplicantController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Hahn.ApplicatonProcess.May2020.Data; +using Hahn.ApplicatonProcess.May2020.Domain; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -15,36 +16,125 @@ namespace Hahn.ApplicatonProcess.May2020.Web.Controllers public class ApplicantController : ControllerBase { private readonly ILogger _logger; + private readonly IApplicantService service; - public ApplicantController(ILogger logger) + public ApplicantController(ILogger logger, IApplicantService service) { _logger = logger; + this.service = service; } [HttpGet("{id}")] - public Applicant Get(int id) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public IActionResult Get(int id) { - throw new NotImplementedException(); + try + { + var obj = service.Get(id); + return Ok(obj); + } + catch (ApplicantMissingExcpetion ex) + { + return NotFound(ex.Message); + } } [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + //If the parsing of applicant fails, the method is not called public IActionResult Post([FromBody] Applicant applicant) { - throw new NotImplementedException(); + try + { + service.Create(applicant); + var applicantID = applicant.ID; + string url = ComposeUrl(applicantID); + return Created(url, applicant); + } + catch (ValidationFailedException ex) + { + return BadRequest(ex.Message); + } + + catch (ApplicantAlreadyExistsException ex) + { + return Conflict(ex.Message); + } + catch (ArgumentNullException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, ex); + } } - [HttpPut("{id}")] - public void Put(int id, Applicant applicant) + [HttpPut] + public IActionResult Put([FromBody] Applicant applicant) { - throw new NotImplementedException(); + try + { + service.Update(applicant); + return StatusCode(StatusCodes.Status200OK, applicant); + } + + catch (ArgumentNullException ex) + { + return BadRequest(ex.Message); + } + catch (ApplicantMissingExcpetion ex) + { + return NotFound(ex.Message); + } + catch (ValidationFailedException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, + ex.Message); + } } [HttpDelete("{id}")] - public void Delete(int id) + public IActionResult Delete(int id) { - throw new NotImplementedException(); + try + { + service.Delete(id); + return StatusCode(StatusCodes.Status200OK); + } + catch (ApplicantMissingExcpetion ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, + ex.Message); + } + } + + private string ComposeUrl(int id) + { + //TODO: + //The Url.RouteUrl is not working and spent some time firguring it + //out and left it. https://github.com/dotnet/aspnetcore/issues/14877 + //This fails in both Integration testing and production. For now + //composing the URL from hand. Possible issues might be versioning + //localization if added. + if (this.Request == null) + return + $"https://{Environment.MachineName}/{nameof(Applicant)}/{id}"; + else + return + $"https://{Request.Host.Value}{Request.Path}/{id}"; } } } diff --git a/Hahn.ApplicatonProcess.May2020.Web/Hahn.ApplicatonProcess.May2020.Web.csproj b/Hahn.ApplicatonProcess.May2020.Web/Hahn.ApplicatonProcess.May2020.Web.csproj index 14b3b8e..8cd308d 100644 --- a/Hahn.ApplicatonProcess.May2020.Web/Hahn.ApplicatonProcess.May2020.Web.csproj +++ b/Hahn.ApplicatonProcess.May2020.Web/Hahn.ApplicatonProcess.May2020.Web.csproj @@ -7,7 +7,6 @@ - diff --git a/Hahn.ApplicatonProcess.May2020.Web/Startup.cs b/Hahn.ApplicatonProcess.May2020.Web/Startup.cs index eebcb5e..9f41d2c 100644 --- a/Hahn.ApplicatonProcess.May2020.Web/Startup.cs +++ b/Hahn.ApplicatonProcess.May2020.Web/Startup.cs @@ -30,13 +30,12 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddSingleton(); + services.AddDbContext(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddDbContext(options => - { - options.UseInMemoryDatabase(nameof(Applicant)); - }); - services.AddApiVersioning(config => { config.ReportApiVersions = true;