-
Notifications
You must be signed in to change notification settings - Fork 18
feat(validation): add DateOfBirth rules and fix Birth null mapping #397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nanotaboada
merged 5 commits into
master
from
feat/player-validation-and-response-formatting
Mar 7, 2026
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6ed4f19
feat(validation): add DateOfBirth rules and fix Birth null mapping (#…
nanotaboada 45f86a7
chore(vscode): add GitHub MCP Server configuration
nanotaboada 635eef2
fix(validation): enforce DateOfBirth constraints and test coverage (#…
nanotaboada 2264c0d
chore(config): add GitHub MCP server to VS Code workspace config
nanotaboada 026d34a
fix(validation): compare date component to reject same-day birth date…
nanotaboada File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| logs/ | ||
| bin/ | ||
| obj/ | ||
| TestResults/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "servers": { | ||
| // GitHub MCP Server - Interact with GitHub APIs (issues, PRs, repos, etc.) | ||
| // https://github.com/github/github-mcp-server | ||
| "github": { | ||
| "type": "stdio", | ||
| "command": "docker", | ||
| "args": [ | ||
| "run", | ||
| "-i", | ||
| "--rm", | ||
| "-e", | ||
| "GITHUB_PERSONAL_ACCESS_TOKEN", | ||
| "ghcr.io/github/github-mcp-server" | ||
| ], | ||
| "env": { | ||
| "GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github_token}" | ||
| } | ||
| } | ||
| }, | ||
| "inputs": [ | ||
| { | ||
| "id": "github_token", | ||
| "type": "promptString", | ||
| "description": "GitHub Personal Access Token. Prefer fine-grained PATs with minimum permissions: Contents (read), Issues (read & write), Pull requests (read & write). If a classic PAT is unavoidable, minimum scope: repo.", | ||
| "password": true | ||
| } | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerValidatorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| 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 Moq; | ||
|
|
||
| namespace Dotnet.Samples.AspNetCore.WebApi.Tests.Unit; | ||
|
|
||
| public class PlayerValidatorTests | ||
| { | ||
| private static PlayerRequestModelValidator CreateValidator( | ||
| Mock<IPlayerRepository>? repositoryMock = null | ||
| ) | ||
| { | ||
| var mock = repositoryMock ?? new Mock<IPlayerRepository>(); | ||
| return new PlayerRequestModelValidator(mock.Object); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * Valid request | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenRequestModelIsValid_ThenValidationShouldPass() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| 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); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeTrue(); | ||
| result.Errors.Should().BeEmpty(); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * FirstName | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenFirstNameIsEmpty_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.FirstName = string.Empty; | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "FirstName"); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * LastName | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenLastNameIsEmpty_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.LastName = string.Empty; | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "LastName"); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * SquadNumber | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenSquadNumberIsNotGreaterThanZero_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.SquadNumber = 0; | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "SquadNumber"); | ||
| } | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenSquadNumberIsNotUnique_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| var existingPlayer = PlayerFakes.MakeNew(); | ||
| var repositoryMock = new Mock<IPlayerRepository>(); | ||
| repositoryMock | ||
| .Setup(r => r.FindBySquadNumberAsync(request.SquadNumber)) | ||
| .ReturnsAsync(existingPlayer); | ||
| var validator = CreateValidator(repositoryMock); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result | ||
| .Errors.Should() | ||
| .Contain(e => e.PropertyName == "SquadNumber" && e.ErrorMessage.Contains("unique")); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * AbbrPosition | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenAbbrPositionIsEmpty_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.AbbrPosition = string.Empty; | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "AbbrPosition"); | ||
| } | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenAbbrPositionIsInvalid_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.AbbrPosition = "INVALID"; | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "AbbrPosition"); | ||
| } | ||
|
|
||
| /* ------------------------------------------------------------------------- | ||
| * DateOfBirth | ||
| * ---------------------------------------------------------------------- */ | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenDateOfBirthIsNull_ThenValidationShouldPass() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.DateOfBirth = null; | ||
| 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); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeTrue(); | ||
| } | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenDateOfBirthIsInTheFuture_ThenValidationShouldFail() | ||
| { | ||
| // Arrange | ||
| var request = PlayerFakes.MakeRequestModelForCreate(); | ||
| request.DateOfBirth = DateTime.UtcNow.AddYears(1); | ||
| var validator = CreateValidator(); | ||
|
|
||
| // Act | ||
| var result = await validator.ValidateAsync(request); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "DateOfBirth"); | ||
| } | ||
|
|
||
| [Fact] | ||
| [Trait("Category", "Unit")] | ||
| public async Task GivenValidateAsync_WhenDateOfBirthIsBeforeYear1900_ThenValidationShouldFail() | ||
| { | ||
| // 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); | ||
|
|
||
| // Assert | ||
| result.IsValid.Should().BeFalse(); | ||
| result.Errors.Should().Contain(e => e.PropertyName == "DateOfBirth"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.