Skip to content

Commit

Permalink
* Implement the CURD API's
Browse files Browse the repository at this point in the history
* Implement validators using Fluent Validation
* Setup Dependency Injection
* Implement custom logic to return location of created resource. Due to a bug with URLHelper implementation (dotnet/aspnetcore#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 `<Project Sdk="Microsoft.NET.Sdk.Web">` 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
  • Loading branch information
soundarmoorthy committed May 30, 2020
1 parent 987f02a commit d8c4ff2
Show file tree
Hide file tree
Showing 26 changed files with 1,540 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -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<WebApplicationFactory<Startup>>, IDisposable
{
private readonly TestObjectFactory test;
private readonly WebApplicationFactory<Startup> factory;

public ApplicationControllerIntegrationTests
(WebApplicationFactory<Startup> 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);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<ValidationFailedException>(() =>
{
service.Create(null);
});

}

[Fact]
public void Create_Throws_Validation_Failed_Exception_On_Invalid_Obj()
{
var service = test.service();
Assert.Throws<ValidationFailedException>(() =>
{
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<ApplicantAlreadyExistsException>(() =>
{
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<ApplicantMissingExcpetion>(() =>
{
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<ApplicantMissingExcpetion>(() =>
{
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<ArgumentNullException>(() =>
{
test.service().Update(null);
});
}

[Fact]
public void Update_Throws_Applicant_Missing_Exception_When_Not_Found()
{
Assert.Throws<ApplicantMissingExcpetion>(() =>
{
test.service().Update(test.applicant());
});
}

[Fact]
public void Update_Throws_Validation_Failed_Exception_When_Invalid()
{
test.db().Add(test.applicant());
Assert.Throws<ValidationFailedException>(() =>
{
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()));
}
}
}
Loading

0 comments on commit d8c4ff2

Please sign in to comment.