first commit
This commit is contained in:
@@ -0,0 +1,544 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Controllers;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Tests.Utilities;
|
||||
using FluentAssertions;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moq;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Tests.Unit;
|
||||
|
||||
public class PlayerControllerTests : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public PlayerControllerTests()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP POST
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Post_Players_ValidationError_Returns422UnprocessableEntity()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(
|
||||
new ValidationResult(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new("SquadNumber", "SquadNumber must be greater than 0."),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.PostAsync(request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Never);
|
||||
service.Verify(service => service.CreateAsync(It.IsAny<PlayerRequestModel>()), Times.Never);
|
||||
validator.Verify(
|
||||
validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status422UnprocessableEntity);
|
||||
var problemDetails = httpResult
|
||||
.ProblemDetails.Should()
|
||||
.BeOfType<HttpValidationProblemDetails>()
|
||||
.Subject;
|
||||
problemDetails.Status.Should().Be(StatusCodes.Status422UnprocessableEntity);
|
||||
problemDetails
|
||||
.Errors.Should()
|
||||
.ContainKey("SquadNumber")
|
||||
.WhoseValue.Should()
|
||||
.Contain("SquadNumber must be greater than 0.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Post_Players_Existing_Returns409Conflict()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var response = PlayerFakes.MakeResponseModelForCreate();
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(response);
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(new ValidationResult(new List<ValidationFailure> { }));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.PostAsync(request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.CreateAsync(It.IsAny<PlayerRequestModel>()), Times.Never);
|
||||
validator.Verify(
|
||||
validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
var httpResult = result.Should().BeOfType<Conflict<ProblemDetails>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status409Conflict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Post_Players_Nonexistent_Returns201Created()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var response = PlayerFakes.MakeResponseModelForCreate();
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(null as PlayerResponseModel);
|
||||
service.Setup(service => service.CreateAsync(request)).ReturnsAsync(response);
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(new ValidationResult(new List<ValidationFailure> { }));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object)
|
||||
{
|
||||
Url = PlayerMocks.SetupUrlHelperMock().Object,
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await controller.PostAsync(request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.CreateAsync(It.IsAny<PlayerRequestModel>()), Times.Once);
|
||||
validator.Verify(
|
||||
validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
var httpResult = result.Should().BeOfType<CreatedAtRoute<PlayerResponseModel>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status201Created);
|
||||
httpResult.Value.Should().BeEquivalentTo(response);
|
||||
httpResult.RouteName.Should().Be("RetrieveBySquadNumber");
|
||||
httpResult.RouteValues.Should().NotBeNull().And.ContainKey("squadNumber");
|
||||
httpResult.RouteValues!["squadNumber"].Should().Be(response.Dorsal);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP GET
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_Players_Existing_ReturnsPlayers()
|
||||
{
|
||||
// Arrange
|
||||
var response = PlayerFakes.MakeResponseModelsForRetrieve();
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service.Setup(service => service.RetrieveAsync()).ReturnsAsync(response);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetAsync();
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveAsync(), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<Ok<List<PlayerResponseModel>>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status200OK);
|
||||
httpResult.Value.Should().NotBeNull().And.BeOfType<List<PlayerResponseModel>>();
|
||||
httpResult.Value.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_Players_Empty_Returns200OkWithEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service.Setup(service => service.RetrieveAsync()).ReturnsAsync([]);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetAsync();
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveAsync(), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<Ok<List<PlayerResponseModel>>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status200OK);
|
||||
httpResult.Value.Should().NotBeNull().And.BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_PlayerById_Unknown_Returns404NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveByIdAsync(id))
|
||||
.ReturnsAsync(null as PlayerResponseModel);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<Guid>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_PlayerById_Existing_Returns200OK()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var response = PlayerFakes.MakeResponseModelForRetrieve(10);
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service.Setup(service => service.RetrieveByIdAsync(id)).ReturnsAsync(response);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveByIdAsync(It.IsAny<Guid>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<Ok<PlayerResponseModel>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status200OK);
|
||||
httpResult.Value.Should().NotBeNull().And.BeOfType<PlayerResponseModel>();
|
||||
httpResult.Value.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_PlayerBySquadNumber_Unknown_Returns404NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as PlayerResponseModel);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetBySquadNumberAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Get_PlayerBySquadNumber_Existing_Returns200OK()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 10;
|
||||
var response = PlayerFakes.MakeResponseModelForRetrieve(squadNumber);
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(response);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.GetBySquadNumberAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<Ok<PlayerResponseModel>>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status200OK);
|
||||
httpResult.Value.Should().NotBeNull().And.BeOfType<PlayerResponseModel>();
|
||||
httpResult.Value.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP PUT
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Put_PlayerBySquadNumber_ValidationError_Returns422UnprocessableEntity()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 20;
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(squadNumber);
|
||||
request.SquadNumber = -999; // Invalid Squad Number
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(
|
||||
new ValidationResult(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new("SquadNumber", "SquadNumber must be greater than 0."),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await controller.PutAsync(squadNumber, request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Never);
|
||||
service.Verify(service => service.UpdateAsync(It.IsAny<PlayerRequestModel>()), Times.Never);
|
||||
validator.Verify(
|
||||
validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status422UnprocessableEntity);
|
||||
var problemDetails = httpResult
|
||||
.ProblemDetails.Should()
|
||||
.BeOfType<HttpValidationProblemDetails>()
|
||||
.Subject;
|
||||
problemDetails.Status.Should().Be(StatusCodes.Status422UnprocessableEntity);
|
||||
problemDetails
|
||||
.Errors.Should()
|
||||
.ContainKey("SquadNumber")
|
||||
.WhoseValue.Should()
|
||||
.Contain("SquadNumber must be greater than 0.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Put_PlayerBySquadNumber_Unknown_Returns404NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as PlayerResponseModel);
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(new ValidationResult(new List<ValidationFailure> { }));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.PutAsync(
|
||||
squadNumber,
|
||||
new PlayerRequestModel { SquadNumber = squadNumber }
|
||||
);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.UpdateAsync(It.IsAny<PlayerRequestModel>()), Times.Never);
|
||||
validator.Verify(
|
||||
validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Put_PlayerBySquadNumber_SquadNumberMismatch_Returns400BadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 23;
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(squadNumber);
|
||||
request.SquadNumber = 99; // Mismatched squad number
|
||||
var response = PlayerFakes.MakeResponseModelForUpdate(squadNumber);
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(response);
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(new ValidationResult(new List<ValidationFailure> { }));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.PutAsync(squadNumber, request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Never);
|
||||
service.Verify(service => service.UpdateAsync(It.IsAny<PlayerRequestModel>()), Times.Never);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Put_PlayerBySquadNumber_Existing_Returns204NoContent()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 23;
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(squadNumber);
|
||||
var response = PlayerFakes.MakeResponseModelForUpdate(squadNumber);
|
||||
request.FirstName = "Emiliano";
|
||||
request.MiddleName = string.Empty;
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(response);
|
||||
service.Setup(service => service.UpdateAsync(request));
|
||||
validator
|
||||
.Setup(validator =>
|
||||
validator.ValidateAsync(
|
||||
It.IsAny<IValidationContext>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ReturnsAsync(new ValidationResult(new List<ValidationFailure> { }));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.PutAsync(squadNumber, request);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.UpdateAsync(It.IsAny<PlayerRequestModel>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<NoContent>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status204NoContent);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP DELETE
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Delete_PlayerBySquadNumber_Unknown_Returns404NotFound()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as PlayerResponseModel);
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.DeleteAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.DeleteAsync(It.IsAny<int>()), Times.Never);
|
||||
var httpResult = result.Should().BeOfType<ProblemHttpResult>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task Delete_PlayerBySquadNumber_Existing_Returns204NoContent()
|
||||
{
|
||||
// Arrange
|
||||
var response = PlayerFakes.MakeResponseModelForCreate();
|
||||
var squadNumber = response.Dorsal;
|
||||
var (service, logger, validator) = PlayerMocks.InitControllerMocks();
|
||||
service
|
||||
.Setup(service => service.RetrieveBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(response);
|
||||
service.Setup(service => service.DeleteAsync(squadNumber));
|
||||
|
||||
var controller = new PlayerController(service.Object, logger.Object, validator.Object);
|
||||
|
||||
// Act
|
||||
var result = await controller.DeleteAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
service.Verify(service => service.RetrieveBySquadNumberAsync(It.IsAny<int>()), Times.Once);
|
||||
service.Verify(service => service.DeleteAsync(It.IsAny<int>()), Times.Once);
|
||||
var httpResult = result.Should().BeOfType<NoContent>().Subject;
|
||||
httpResult.StatusCode.Should().Be(StatusCodes.Status204NoContent);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", null);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
using System.Diagnostics;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Services;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Tests.Utilities;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Tests.Unit;
|
||||
|
||||
public class PlayerServiceTests
|
||||
{
|
||||
/* -------------------------------------------------------------------------
|
||||
* Create
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task CreateAsync_WhenCalled_AddsPlayerAndRemovesCache()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var response = PlayerFakes.MakeResponseModelForCreate();
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
mapper.Setup(mapper => mapper.Map<PlayerResponseModel>(request)).Returns(response);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.CreateAsync(request);
|
||||
|
||||
// Assert
|
||||
repository.Verify(repository => repository.AddAsync(It.IsAny<Player>()), Times.Once);
|
||||
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Once);
|
||||
mapper.Verify(
|
||||
mapper => mapper.Map<PlayerResponseModel>(It.IsAny<PlayerRequestModel>()),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Retrieve
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveAsync_CacheMiss_QueriesRepositoryAndCachesResult()
|
||||
{
|
||||
// Arrange
|
||||
var value = It.IsAny<object>();
|
||||
var players = PlayerFakes.MakeStarting11();
|
||||
var response = PlayerFakes.MakeResponseModelsForRetrieve();
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository.Setup(repository => repository.GetAllAsync()).ReturnsAsync(players);
|
||||
mapper.Setup(mapper => mapper.Map<List<PlayerResponseModel>>(players)).Returns(response);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await service.RetrieveAsync();
|
||||
|
||||
// Assert
|
||||
repository.Verify(repository => repository.GetAllAsync(), Times.Once);
|
||||
memoryCache.Verify(cache => cache.TryGetValue(It.IsAny<object>(), out value), Times.Once);
|
||||
memoryCache.Verify(cache => cache.CreateEntry(It.IsAny<object>()), Times.Once);
|
||||
mapper.Verify(
|
||||
mapper => mapper.Map<List<PlayerResponseModel>>(It.IsAny<List<Player>>()),
|
||||
Times.Once
|
||||
);
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveAsync_CacheHit_ReturnsBeforeQueryingRepository()
|
||||
{
|
||||
// Arrange
|
||||
var value = It.IsAny<object>();
|
||||
var players = PlayerFakes.MakeStarting11();
|
||||
var response = PlayerFakes.MakeResponseModelsForRetrieve();
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository.Setup(repository => repository.GetAllAsync()).ReturnsAsync(players);
|
||||
mapper.Setup(mapper => mapper.Map<List<PlayerResponseModel>>(players)).Returns(response);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var first = await ExecutionTimeAsync(() => service.RetrieveAsync());
|
||||
var second = await ExecutionTimeAsync(() => service.RetrieveAsync());
|
||||
|
||||
// Assert
|
||||
memoryCache.Verify(
|
||||
cache => cache.TryGetValue(It.IsAny<object>(), out value),
|
||||
Times.Exactly(2) // first + second
|
||||
);
|
||||
memoryCache.Verify(cache => cache.CreateEntry(It.IsAny<object>()), Times.Once); // first only
|
||||
repository.Verify(repository => repository.GetAllAsync(), Times.Once); // first only
|
||||
mapper.Verify(
|
||||
mapper => mapper.Map<List<PlayerResponseModel>>(It.IsAny<List<Player>>()),
|
||||
Times.Once // first only
|
||||
);
|
||||
second.Should().BeLessThan(first);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveByIdAsync_PlayerNotFound_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository.Setup(repository => repository.FindByIdAsync(id)).ReturnsAsync(null as Player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await service.RetrieveByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
repository.Verify(repository => repository.FindByIdAsync(It.IsAny<Guid>()), Times.Once);
|
||||
mapper.Verify(mapper => mapper.Map<PlayerResponseModel>(It.IsAny<Player>()), Times.Never);
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveByIdAsync_PlayerFound_ReturnsMappedResponseModel()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var squadNumber = 10;
|
||||
var player = PlayerFakes.MakeFromStarting11(squadNumber);
|
||||
var response = PlayerFakes.MakeResponseModelForRetrieve(squadNumber);
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository.Setup(repository => repository.FindByIdAsync(id)).ReturnsAsync(player);
|
||||
mapper.Setup(mapper => mapper.Map<PlayerResponseModel>(player)).Returns(response);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await service.RetrieveByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
repository.Verify(repository => repository.FindByIdAsync(It.IsAny<Guid>()), Times.Once);
|
||||
mapper.Verify(mapper => mapper.Map<PlayerResponseModel>(It.IsAny<Player>()), Times.Once);
|
||||
result.Should().BeOfType<PlayerResponseModel>();
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveBySquadNumberAsync_PlayerNotFound_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await service.RetrieveBySquadNumberAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
mapper.Verify(mapper => mapper.Map<PlayerResponseModel>(It.IsAny<Player>()), Times.Never);
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task RetrieveBySquadNumberAsync_PlayerFound_ReturnsMappedResponseModel()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 10;
|
||||
var player = PlayerFakes.MakeFromStarting11(squadNumber);
|
||||
var response = PlayerFakes.MakeResponseModelForRetrieve(squadNumber);
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(player);
|
||||
mapper.Setup(mapper => mapper.Map<PlayerResponseModel>(player)).Returns(response);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await service.RetrieveBySquadNumberAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
mapper.Verify(mapper => mapper.Map<PlayerResponseModel>(It.IsAny<Player>()), Times.Once);
|
||||
result.Should().BeOfType<PlayerResponseModel>();
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Update
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task UpdateAsync_PlayerFound_UpdatesRepositoryAndRemovesCache()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 23;
|
||||
var player = PlayerFakes.MakeFromStarting11(squadNumber);
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(squadNumber);
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
repository.Verify(repository => repository.UpdateAsync(It.IsAny<Player>()), Times.Once);
|
||||
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Once);
|
||||
mapper.Verify(
|
||||
mapper => mapper.Map(It.IsAny<PlayerRequestModel>(), It.IsAny<Player>()),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task UpdateAsync_PlayerNotFound_DoesNotUpdateRepository()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.SquadNumber = squadNumber;
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.UpdateAsync(request);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
repository.Verify(repository => repository.UpdateAsync(It.IsAny<Player>()), Times.Never);
|
||||
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Never);
|
||||
mapper.Verify(
|
||||
mapper => mapper.Map(It.IsAny<PlayerRequestModel>(), It.IsAny<Player>()),
|
||||
Times.Never
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Delete
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task DeleteAsync_PlayerFound_RemovesFromRepositoryAndRemovesCache()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 26;
|
||||
var player = PlayerFakes.MakeFromStarting11(squadNumber);
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.DeleteAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
repository.Verify(repository => repository.RemoveAsync(It.IsAny<Guid>()), Times.Once);
|
||||
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task DeleteAsync_PlayerNotFound_DoesNotRemoveFromRepository()
|
||||
{
|
||||
// Arrange
|
||||
var squadNumber = 999;
|
||||
var (repository, logger, memoryCache, mapper, environment) = PlayerMocks.InitServiceMocks();
|
||||
repository
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(squadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
|
||||
var service = new PlayerService(
|
||||
repository.Object,
|
||||
logger.Object,
|
||||
memoryCache.Object,
|
||||
mapper.Object,
|
||||
environment.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.DeleteAsync(squadNumber);
|
||||
|
||||
// Assert
|
||||
repository.Verify(
|
||||
repository => repository.FindBySquadNumberAsync(It.IsAny<int>()),
|
||||
Times.Once
|
||||
);
|
||||
repository.Verify(repository => repository.RemoveAsync(It.IsAny<Guid>()), Times.Never);
|
||||
memoryCache.Verify(cache => cache.Remove(It.IsAny<object>()), Times.Never);
|
||||
}
|
||||
|
||||
private static async Task<long> ExecutionTimeAsync(Func<Task> awaitable)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
|
||||
stopwatch.Start();
|
||||
await awaitable();
|
||||
stopwatch.Stop();
|
||||
|
||||
return stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Tests.Utilities;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Validators;
|
||||
using FluentAssertions;
|
||||
using FluentValidation;
|
||||
using Moq;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Tests.Unit;
|
||||
|
||||
public class PlayerValidatorTests
|
||||
{
|
||||
private sealed class FakeTimeProvider(DateTimeOffset utcNow) : TimeProvider
|
||||
{
|
||||
public override DateTimeOffset GetUtcNow() => utcNow;
|
||||
}
|
||||
|
||||
private static PlayerRequestModelValidator CreateValidator(
|
||||
Mock<IPlayerRepository>? repositoryMock = null,
|
||||
TimeProvider? timeProvider = null
|
||||
)
|
||||
{
|
||||
var mock = repositoryMock ?? new Mock<IPlayerRepository>();
|
||||
return new PlayerRequestModelValidator(mock.Object, timeProvider);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Valid request
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_ValidRequest_ReturnsNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var repositoryMock = new Mock<IPlayerRepository>();
|
||||
repositoryMock
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
var validator = CreateValidator(repositoryMock);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.Errors.Should().BeEmpty();
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* FirstName
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_FirstNameEmpty_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.FirstName = string.Empty;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "FirstName");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* LastName
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_LastNameEmpty_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.LastName = string.Empty;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "LastName");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* SquadNumber
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_SquadNumberNotGreaterThanZero_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.SquadNumber = 0;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "SquadNumber");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_SquadNumberNegative_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.SquadNumber = -5;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result
|
||||
.Errors.Should()
|
||||
.Contain(error =>
|
||||
error.PropertyName == "SquadNumber" && error.ErrorMessage.Contains("greater than 0")
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_SquadNumberNotUnique_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
var existingPlayer = PlayerFakes.MakeNew();
|
||||
var repositoryMock = new Mock<IPlayerRepository>();
|
||||
repositoryMock
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(existingPlayer);
|
||||
var validator = CreateValidator(repositoryMock);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result
|
||||
.Errors.Should()
|
||||
.Contain(error =>
|
||||
error.PropertyName == "SquadNumber" && error.ErrorMessage.Contains("unique")
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_SquadNumberBelongsToPlayerBeingUpdated_ReturnsNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
// Simulate a PUT request for an existing player: the squad number in the
|
||||
// body matches the one already in the database. The "Update" rule set must
|
||||
// not run BeUniqueSquadNumber, otherwise this valid request would be rejected.
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(10);
|
||||
var existingPlayer = PlayerFakes.MakeFromStarting11(10);
|
||||
var repositoryMock = new Mock<IPlayerRepository>();
|
||||
repositoryMock
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(existingPlayer);
|
||||
var validator = CreateValidator(repositoryMock);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Update")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.Errors.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_FirstNameEmptyInUpdateRuleSet_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForUpdate(10);
|
||||
request.FirstName = string.Empty;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Update")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "FirstName");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* AbbrPosition
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_AbbrPositionEmpty_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.AbbrPosition = string.Empty;
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "AbbrPosition");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_AbbrPositionInvalid_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.AbbrPosition = "INVALID";
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "AbbrPosition");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* DateOfBirth
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_DateOfBirthNull_ReturnsNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.DateOfBirth = null;
|
||||
var repositoryMock = new Mock<IPlayerRepository>();
|
||||
repositoryMock
|
||||
.Setup(repository => repository.FindBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
var validator = CreateValidator(repositoryMock);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_DateOfBirthInFuture_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.DateOfBirth = DateTime.UtcNow.AddYears(1);
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "DateOfBirth");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_DateOfBirthToday_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var fixedNow = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var timeProvider = new FakeTimeProvider(fixedNow);
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.DateOfBirth = fixedNow.Date; // same "today" the validator sees
|
||||
var validator = CreateValidator(timeProvider: timeProvider);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == "DateOfBirth");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_DateOfBirthBeforeYear1900_ReturnsValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.DateOfBirth = new DateTime(1899, 12, 31, 0, 0, 0, DateTimeKind.Utc);
|
||||
var validator = CreateValidator();
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(error => error.PropertyName == "DateOfBirth");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
public async Task ValidateAsync_DateOfBirthOnJanuary1st1900_ReturnsNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
var request = PlayerFakes.MakeRequestModelForCreate();
|
||||
request.DateOfBirth = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc); // rule: >= 1900-01-01
|
||||
var repositoryMock = new Mock<IPlayerRepository>();
|
||||
repositoryMock
|
||||
.Setup(r => r.FindBySquadNumberAsync(request.SquadNumber))
|
||||
.ReturnsAsync(null as Player);
|
||||
var validator = CreateValidator(repositoryMock);
|
||||
|
||||
// Act
|
||||
var result = await validator.ValidateAsync(
|
||||
request,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.Errors.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user