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;