diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 7b780e1..8f0cb20 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -77,6 +77,28 @@ reviews: - Ensure error messages are descriptive - Verify validators are registered in DI container + - path: "src/**/Mappings/**/*.cs" + instructions: | + - Profiles should inherit from AutoMapper Profile + - Verify CreateMap() calls cover all DTOs in use + - Check reverse mappings are defined where bidirectional mapping is needed + - Ensure profiles are registered via AddAutoMapper() in the DI setup + - Flag any manual property mappings that could be replaced by convention + + - path: "src/**/Middlewares/**/*.cs" + instructions: | + - Middleware must implement InvokeAsync(HttpContext context) signature + - Verify _next(context) is called unless the middleware intentionally short-circuits + - Ensure exceptions are not swallowed — rethrow or translate to Problem Details + - Middleware should be stateless; dependencies requiring request scope belong in services + + - path: "src/**/Extensions/**/*.cs" + instructions: | + - Extension methods should target IServiceCollection and return IServiceCollection + - Each method should encapsulate a single registration concern + - Verify service lifetimes (Singleton/Scoped/Transient) match the dependency's usage + - Follow the Add{Feature} naming convention (e.g. AddPlayerServices, AddRateLimiting) + - path: "test/**/*.cs" instructions: | - Tests should use xUnit, Moq, and FluentAssertions diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 0000000..3010202 --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,95 @@ +# ============================================================================= +# SonarCloud Automatic Analysis configuration +# https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/automatic-analysis/ +# +# SonarCloud's automatic analysis (GitHub App, no CI workflow) reads THIS file. +# Wildcard patterns are NOT supported here, so all exclusion paths must be +# listed explicitly. +# ============================================================================= + +sonar.sources=src/ +sonar.tests=test/ +sonar.sourceEncoding=UTF-8 + +# ============================================================================= +# Global exclusions +# ============================================================================= + +sonar.exclusions=\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs + +# ============================================================================= +# Coverage exclusions +# Test files and generated/infrastructure code should not count against +# production code coverage metrics. +# ============================================================================= + +sonar.coverage.exclusions=\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerServiceTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerValidatorTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/DatabaseFakes.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerMocks.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerStubs.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Usings.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Data/PlayerDbContext.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Program.cs + +# ============================================================================= +# Duplicate code (CPD) exclusions +# Automatic analysis does not support wildcard patterns, so each file is +# listed explicitly. +# +# Migrations — EF Core migration files are intentionally repetitive +# (sequential InsertData/UpdateData/Sql calls). +# +# PlayerDbContext.cs — scaffolded EF Core infrastructure. +# +# PlayerData.cs — parallel WithId/without-Id method pairs mirror each other +# by design; duplication is structural, not accidental. +# +# PlayerRequestModelValidator.cs — the "Create" and "Update" rule sets share +# common rules by design; duplication is intentional. +# +# Test files — Fakes, Mocks, and Stubs are intentionally repetitive by design. +# ============================================================================= + +sonar.cpd.exclusions=\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414191223_InitialCreate.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20250414195445_SeedStarting11.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Data/PlayerDbContext.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs,\ + src/Dotnet.Samples.AspNetCore.WebApi/Validators/PlayerRequestModelValidator.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerServiceTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerValidatorTests.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/DatabaseFakes.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerMocks.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerStubs.cs,\ + test/Dotnet.Samples.AspNetCore.WebApi.Tests/Usings.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c48b7..63a0dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc ### Changed +- Normalize player dataset: add Lo Celso (squad 27), correct Fernández/Mac Allister/Messi team data, replace random UUIDs with deterministic UUID v5 values (#435) +- Align CRUD test fixtures: Lo Celso (squad 27) for Create and Delete, Messi (squad 10) for Retrieve, Damián Martínez (squad 23) for Update (#435) - Bump `codecov/codecov-action` from 5.5.2 to 5.5.3 (#423) ### Fixed diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index b019777..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,61 +0,0 @@ -# ============================================================================= -# SonarCloud configuration -# https://docs.sonarsource.com/sonarcloud/advanced-setup/analysis-parameters/ -# -# Project key and organization can be verified in SonarCloud under: -# Administration → General Settings -# ============================================================================= - -sonar.projectKey=nanotaboada_Dotnet.Samples.AspNetCore.WebApi -sonar.organization=nanotaboada - -# Source encoding -sonar.sourceEncoding=UTF-8 - -# ============================================================================= -# Sources and tests -# ============================================================================= - -sonar.sources=src/ -sonar.tests=test/ -sonar.test.inclusions=test/**/*.cs - -# ============================================================================= -# Global exclusions -# Keeps analysis focused on hand-written production code. -# ============================================================================= - -sonar.exclusions=\ - **/obj/**,\ - **/bin/**,\ - **/Migrations/** - -# ============================================================================= -# Coverage exclusions -# Test files and generated/infrastructure code should not count against -# production code coverage metrics. -# ============================================================================= - -sonar.coverage.exclusions=\ - test/**/*.cs,\ - **/Migrations/**,\ - **/Data/PlayerDbContext.cs - -# ============================================================================= -# Duplicate code (CPD) exclusions -# NOTE: // NOSONAR suppresses rule-based issues (bugs, code smells, security -# hotspots) but has no effect on CPD metrics. Files must be listed here to be -# excluded from duplicate code detection. -# -# test/**/*.cs — Fakes, Mocks, and Stubs are intentionally repetitive by -# design; similarity between arrange/act/assert blocks across tests is -# expected and harmless. -# -# **/Validators/PlayerRequestModelValidator.cs — the "Create" and "Update" -# rule sets share common rules by design; the duplication is intentional to -# keep each operation's validation self-contained and readable. -# ============================================================================= - -sonar.cpd.exclusions=\ - test/**/*.cs,\ - **/Validators/PlayerRequestModelValidator.cs diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs new file mode 100644 index 0000000..33c29f9 --- /dev/null +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.Designer.cs @@ -0,0 +1,69 @@ +// +using System; +using Dotnet.Samples.AspNetCore.WebApi.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Dotnet.Samples.AspNetCore.WebApi.Migrations +{ + [DbContext(typeof(PlayerDbContext))] + [Migration("20260329000000_NormalizePlayerDataset")] + partial class NormalizePlayerDataset + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); + + modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AbbrPosition") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("League") + .HasColumnType("TEXT"); + + b.Property("MiddleName") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("TEXT"); + + b.Property("SquadNumber") + .HasColumnType("INTEGER"); + + b.Property("Starting11") + .HasColumnType("INTEGER"); + + b.Property("Team") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SquadNumber") + .IsUnique(); + + b.ToTable("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs new file mode 100644 index 0000000..5ab5b08 --- /dev/null +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20260329000000_NormalizePlayerDataset.cs @@ -0,0 +1,193 @@ +using Dotnet.Samples.AspNetCore.WebApi.Utilities; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Dotnet.Samples.AspNetCore.WebApi.Migrations +{ + /// + public partial class NormalizePlayerDataset : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Update UUIDs to canonical UUID v5 values for starting 11 + migrationBuilder.Sql( + "UPDATE Players SET Id = '01772c59-43f0-5d85-b913-c78e4e281452' WHERE SquadNumber = 23" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'da31293b-4c7e-5e0f-a168-469ee29ecbc4' WHERE SquadNumber = 26" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'c096c69e-762b-5281-9290-bb9c167a24a0' WHERE SquadNumber = 13" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd5f7dd7a-1dcb-5960-ba27-e34865b63358' WHERE SquadNumber = 19" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '2f6f90a0-9b9d-5023-96d2-a2aaf03143a6' WHERE SquadNumber = 3" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'b5b46e79-929e-5ed2-949d-0d167109c022' WHERE SquadNumber = 11" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '0293b282-1da8-562e-998e-83849b417a42' WHERE SquadNumber = 7" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd3ba552a-dac3-588a-b961-1ea7224017fd', Team = 'SL Benfica', League = 'Liga Portugal' WHERE SquadNumber = 24" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '9613cae9-16ab-5b54-937e-3135123b9e0d', Team = 'Brighton & Hove Albion' WHERE SquadNumber = 20" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'acc433bf-d505-51fe-831e-45eb44c4d43c', Team = 'Paris Saint-Germain', League = 'Ligue 1' WHERE SquadNumber = 10" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '38bae91d-8519-55a2-b30a-b9fe38849bfb' WHERE SquadNumber = 9" + ); + + // Update UUIDs to canonical UUID v5 values for substitutes + migrationBuilder.Sql( + "UPDATE Players SET Id = '5a9cd988-95e6-54c1-bc34-9aa08acca8d0' WHERE SquadNumber = 1" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'c62f2ac1-41e8-5d34-b073-2ba0913d0e31' WHERE SquadNumber = 12" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '5fdb10e8-38c0-5084-9a3f-b369a960b9c2' WHERE SquadNumber = 2" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'bbd441f7-fcfb-5834-8468-2a9004b64c8c' WHERE SquadNumber = 4" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd8bfea25-f189-5d5e-b3a5-ed89329b9f7c' WHERE SquadNumber = 6" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'dca343a8-12e5-53d6-89a8-916b120a5ee4' WHERE SquadNumber = 8" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '98306555-a466-5d18-804e-dc82175e697b' WHERE SquadNumber = 25" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '9d140400-196f-55d8-86e1-e0b96a375c83' WHERE SquadNumber = 5" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd3b0e8e8-2c34-531a-b608-b24fed0ef986' WHERE SquadNumber = 14" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '7cc8d527-56a2-58bd-9528-2618fc139d30' WHERE SquadNumber = 17" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '191c82af-0c51-526a-b903-c3600b61b506' WHERE SquadNumber = 18" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba' WHERE SquadNumber = 15" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'ecec27e8-487b-5622-b116-0855020477ed' WHERE SquadNumber = 16" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '7941cd7c-4df1-5952-97e8-1e7f5d08e8aa' WHERE SquadNumber = 21" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '79c96f29-c59f-5f98-96b8-3a5946246624' WHERE SquadNumber = 22" + ); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Revert team/league corrections + migrationBuilder.Sql( + "UPDATE Players SET Team = 'Chelsea FC', League = 'Premier League' WHERE SquadNumber = 24" + ); + migrationBuilder.Sql("UPDATE Players SET Team = 'Liverpool FC' WHERE SquadNumber = 20"); + migrationBuilder.Sql( + "UPDATE Players SET Team = 'Inter Miami CF', League = 'Major League Soccer' WHERE SquadNumber = 10" + ); + + // Revert UUIDs for starting 11 + migrationBuilder.Sql( + "UPDATE Players SET Id = 'f91b9671-cfd1-48d7-afb9-34e93568c9ee' WHERE SquadNumber = 23" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '51ec988a-0d8b-42d9-84e4-5e17c93d8d50' WHERE SquadNumber = 26" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '0969be24-086c-4c51-9c29-0280683b8133' WHERE SquadNumber = 13" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'ac532709-4682-49db-acc2-395f61f405ab' WHERE SquadNumber = 19" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'dc052ee4-c69d-49da-a256-b8ec727f4d59' WHERE SquadNumber = 3" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '6def9bb7-23c2-42b5-b37b-2e9b6fec31cd' WHERE SquadNumber = 11" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '8ca911d9-ab32-4366-b2b1-cad5eb6f4bcc' WHERE SquadNumber = 7" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '198c4774-9607-4e76-8475-ec2528af69d2' WHERE SquadNumber = 24" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '06971ada-1b3d-4d4a-88f5-e2f35311b5aa' WHERE SquadNumber = 20" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'df6f6bab-5efd-4518-80bb-09ef54435636' WHERE SquadNumber = 10" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = '27cf4e29-67d5-4c3b-9cf8-7d3fa3942fcb' WHERE SquadNumber = 9" + ); + + // Revert UUIDs for substitutes + migrationBuilder.Sql( + "UPDATE Players SET Id = 'b1f8a5d3-2c4e-4a6b-8d9f-1e3c5a7b9d2f' WHERE SquadNumber = 1" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'c2e9b6f4-3d5f-4b7c-9e0a-2f4d6b8c0e3a' WHERE SquadNumber = 12" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd3f0c7e5-4e6a-5c8d-0f1b-3a5e7c9d1f4b' WHERE SquadNumber = 2" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'e4a1d8f6-5f7b-6d9e-1a2c-4b6d8e0a2c5d' WHERE SquadNumber = 4" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'f5b2e9a7-6a8c-7e0f-2b3d-5c7e9a1b3d6e' WHERE SquadNumber = 6" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'a6c3f0b8-7b9d-8f1a-3c4e-6d8f0b2c4e7f' WHERE SquadNumber = 8" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'b7d4a1c9-8c0e-9a2b-4d5f-7e9a1c3d5f8a' WHERE SquadNumber = 25" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'c8e5b2d0-9d1f-0b3c-5e6a-8f0b2d4e6a9b' WHERE SquadNumber = 5" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd9f6c3e1-0e2a-1c4d-6f7b-9a1c3e5f7b0c' WHERE SquadNumber = 14" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'e0a7d4f2-1f3b-2d5e-7a8c-0b2d4f6a8c1d' WHERE SquadNumber = 17" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'f1b8e5a3-2a4c-3e6f-8b9d-1c3e5a7b9d2e' WHERE SquadNumber = 18" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'a2c9f6b4-3b5d-4f7a-9c0e-2d4f6b8c0e3f' WHERE SquadNumber = 15" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'b3d0a7c5-4c6e-5a8b-0d1f-3e5a7c9d1f4a' WHERE SquadNumber = 16" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'c4e1b8d6-5d7f-6b9c-1e2a-4f6d8e0a2c5b' WHERE SquadNumber = 21" + ); + migrationBuilder.Sql( + "UPDATE Players SET Id = 'd5f2c9e7-6e8a-7c0d-2f3b-5a7e9a1b3d6c' WHERE SquadNumber = 22" + ); + } + } +} diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs index 6facad1..500a394 100644 --- a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs @@ -118,8 +118,8 @@ public static List MakeStarting11() SquadNumber = 24, Position = Position.CentralMidfield.Text, AbbrPosition = Position.CentralMidfield.Abbr, - Team = "Chelsea FC", - League = "Premier League", + Team = "SL Benfica", + League = "Liga Portugal", Starting11 = true, }, new() @@ -130,7 +130,7 @@ public static List MakeStarting11() SquadNumber = 20, Position = Position.CentralMidfield.Text, AbbrPosition = Position.CentralMidfield.Abbr, - Team = "Liverpool FC", + Team = "Brighton & Hove Albion", League = "Premier League", Starting11 = true, }, @@ -143,8 +143,8 @@ public static List MakeStarting11() SquadNumber = 10, Position = Position.RightWinger.Text, AbbrPosition = Position.RightWinger.Abbr, - Team = "Inter Miami CF", - League = "Major League Soccer", + Team = "Paris Saint-Germain", + League = "Ligue 1", Starting11 = true, }, new() @@ -158,7 +158,7 @@ public static List MakeStarting11() Team = "Manchester City", League = "Premier League", Starting11 = true, - } + }, ]; } @@ -172,7 +172,7 @@ public static List MakeStarting11WithId() [ new() { - Id = Guid.Parse("f91b9671-cfd1-48d7-afb9-34e93568c9ee"), + Id = Guid.Parse("01772c59-43f0-5d85-b913-c78e4e281452"), FirstName = "Damián", MiddleName = "Emiliano", LastName = "Martínez", @@ -186,7 +186,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("51ec988a-0d8b-42d9-84e4-5e17c93d8d50"), + Id = Guid.Parse("da31293b-4c7e-5e0f-a168-469ee29ecbc4"), FirstName = "Nahuel", LastName = "Molina", DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, DateTimeKind.Utc), @@ -199,7 +199,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("0969be24-086c-4c51-9c29-0280683b8133"), + Id = Guid.Parse("c096c69e-762b-5281-9290-bb9c167a24a0"), FirstName = "Cristian", MiddleName = "Gabriel", LastName = "Romero", @@ -213,7 +213,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("ac532709-4682-49db-acc2-395f61f405ab"), + Id = Guid.Parse("d5f7dd7a-1dcb-5960-ba27-e34865b63358"), FirstName = "Nicolás", MiddleName = "Hernán Gonzalo", LastName = "Otamendi", @@ -227,7 +227,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("dc052ee4-c69d-49da-a256-b8ec727f4d59"), + Id = Guid.Parse("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"), FirstName = "Nicolás", MiddleName = "Alejandro", LastName = "Tagliafico", @@ -241,7 +241,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("6def9bb7-23c2-42b5-b37b-2e9b6fec31cd"), + Id = Guid.Parse("b5b46e79-929e-5ed2-949d-0d167109c022"), FirstName = "Ángel", MiddleName = "Fabián", LastName = "Di María", @@ -255,7 +255,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("8ca911d9-ab32-4366-b2b1-cad5eb6f4bcc"), + Id = Guid.Parse("0293b282-1da8-562e-998e-83849b417a42"), FirstName = "Rodrigo", MiddleName = "Javier", LastName = "de Paul", @@ -269,7 +269,7 @@ public static List MakeStarting11WithId() }, new() { - Id = Guid.Parse("198c4774-9607-4e76-8475-ec2528af69d2"), + Id = Guid.Parse("d3ba552a-dac3-588a-b961-1ea7224017fd"), FirstName = "Enzo", MiddleName = "Jeremías", LastName = "Fernández", @@ -277,26 +277,26 @@ public static List MakeStarting11WithId() SquadNumber = 24, Position = Position.CentralMidfield.Text, AbbrPosition = Position.CentralMidfield.Abbr, - Team = "Chelsea FC", - League = "Premier League", + Team = "SL Benfica", + League = "Liga Portugal", Starting11 = true, }, new() { - Id = Guid.Parse("06971ada-1b3d-4d4a-88f5-e2f35311b5aa"), + Id = Guid.Parse("9613cae9-16ab-5b54-937e-3135123b9e0d"), FirstName = "Alexis", LastName = "Mac Allister", DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, DateTimeKind.Utc), SquadNumber = 20, Position = Position.CentralMidfield.Text, AbbrPosition = Position.CentralMidfield.Abbr, - Team = "Liverpool FC", + Team = "Brighton & Hove Albion", League = "Premier League", Starting11 = true, }, new() { - Id = Guid.Parse("df6f6bab-5efd-4518-80bb-09ef54435636"), + Id = Guid.Parse("acc433bf-d505-51fe-831e-45eb44c4d43c"), FirstName = "Lionel", MiddleName = "Andrés", LastName = "Messi", @@ -304,13 +304,13 @@ public static List MakeStarting11WithId() SquadNumber = 10, Position = Position.RightWinger.Text, AbbrPosition = Position.RightWinger.Abbr, - Team = "Inter Miami CF", - League = "Major League Soccer", + Team = "Paris Saint-Germain", + League = "Ligue 1", Starting11 = true, }, new() { - Id = Guid.Parse("27cf4e29-67d5-4c3b-9cf8-7d3fa3942fcb"), + Id = Guid.Parse("38bae91d-8519-55a2-b30a-b9fe38849bfb"), FirstName = "Julián", LastName = "Álvarez", DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, DateTimeKind.Utc), @@ -320,7 +320,7 @@ public static List MakeStarting11WithId() Team = "Manchester City", League = "Premier League", Starting11 = true, - } + }, ]; } @@ -523,21 +523,33 @@ public static List GetSubstitutes() Team = "Inter Milan", League = "Serie A", Starting11 = false, - } + }, + new() + { + FirstName = "Giovani", + LastName = "Lo Celso", + DateOfBirth = new DateTime(1996, 4, 9, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 27, + Position = Position.CentralMidfield.Text, + AbbrPosition = Position.CentralMidfield.Abbr, + Team = "Real Betis Balompié", + League = "La Liga", + Starting11 = false, + }, ]; } /// /// Create a predefined list of 15 substitute players where each player has a fixed GUID identifier and full profile data. /// - /// A list of 15 Player instances representing substitute players; each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. + /// A list of 15 Player instances representing substitute players (squads 1–26); each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. Lo Celso (squad 27) is intentionally excluded — his squad number falls outside the seeded range so he can serve as the canonical Create/Delete fixture without conflicting with seeded data. public static List GetSubstitutesWithId() { return [ new() { - Id = Guid.Parse("b1f8a5d3-2c4e-4a6b-8d9f-1e3c5a7b9d2f"), + Id = Guid.Parse("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"), FirstName = "Franco", MiddleName = "Daniel", LastName = "Armani", @@ -551,7 +563,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("c2e9b6f4-3d5f-4b7c-9e0a-2f4d6b8c0e3a"), + Id = Guid.Parse("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"), FirstName = "Gerónimo", LastName = "Rulli", DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, DateTimeKind.Utc), @@ -564,7 +576,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("d3f0c7e5-4e6a-5c8d-0f1b-3a5e7c9d1f4b"), + Id = Guid.Parse("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"), FirstName = "Juan", MiddleName = "Marcos", LastName = "Foyth", @@ -578,7 +590,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("e4a1d8f6-5f7b-6d9e-1a2c-4b6d8e0a2c5d"), + Id = Guid.Parse("bbd441f7-fcfb-5834-8468-2a9004b64c8c"), FirstName = "Gonzalo", MiddleName = "Ariel", LastName = "Montiel", @@ -592,7 +604,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("f5b2e9a7-6a8c-7e0f-2b3d-5c7e9a1b3d6e"), + Id = Guid.Parse("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"), FirstName = "Germán", MiddleName = "Alejo", LastName = "Pezzella", @@ -606,7 +618,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("a6c3f0b8-7b9d-8f1a-3c4e-6d8f0b2c4e7f"), + Id = Guid.Parse("dca343a8-12e5-53d6-89a8-916b120a5ee4"), FirstName = "Marcos", MiddleName = "Javier", LastName = "Acuña", @@ -620,7 +632,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("b7d4a1c9-8c0e-9a2b-4d5f-7e9a1c3d5f8a"), + Id = Guid.Parse("98306555-a466-5d18-804e-dc82175e697b"), FirstName = "Lisandro", LastName = "Martínez", DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, DateTimeKind.Utc), @@ -633,7 +645,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("c8e5b2d0-9d1f-0b3c-5e6a-8f0b2d4e6a9b"), + Id = Guid.Parse("9d140400-196f-55d8-86e1-e0b96a375c83"), FirstName = "Leandro", MiddleName = "Daniel", LastName = "Paredes", @@ -647,7 +659,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("d9f6c3e1-0e2a-1c4d-6f7b-9a1c3e5f7b0c"), + Id = Guid.Parse("d3b0e8e8-2c34-531a-b608-b24fed0ef986"), FirstName = "Exequiel", MiddleName = "Alejandro", LastName = "Palacios", @@ -661,7 +673,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("e0a7d4f2-1f3b-2d5e-7a8c-0b2d4f6a8c1d"), + Id = Guid.Parse("7cc8d527-56a2-58bd-9528-2618fc139d30"), FirstName = "Alejandro", MiddleName = "Darío", LastName = "Gómez", @@ -675,7 +687,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("f1b8e5a3-2a4c-3e6f-8b9d-1c3e5a7b9d2e"), + Id = Guid.Parse("191c82af-0c51-526a-b903-c3600b61b506"), FirstName = "Guido", LastName = "Rodríguez", DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, DateTimeKind.Utc), @@ -688,7 +700,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("a2c9f6b4-3b5d-4f7a-9c0e-2d4f6b8c0e3f"), + Id = Guid.Parse("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"), FirstName = "Ángel", MiddleName = "Martín", LastName = "Correa", @@ -702,7 +714,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("b3d0a7c5-4c6e-5a8b-0d1f-3e5a7c9d1f4a"), + Id = Guid.Parse("ecec27e8-487b-5622-b116-0855020477ed"), FirstName = "Thiago", MiddleName = "Ezequiel", LastName = "Almada", @@ -716,7 +728,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("c4e1b8d6-5d7f-6b9c-1e2a-4f6d8e0a2c5b"), + Id = Guid.Parse("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"), FirstName = "Paulo", MiddleName = "Exequiel", LastName = "Dybala", @@ -730,7 +742,7 @@ public static List GetSubstitutesWithId() }, new() { - Id = Guid.Parse("d5f2c9e7-6e8a-7c0d-2f3b-5a7e9a1b3d6c"), + Id = Guid.Parse("79c96f29-c59f-5f98-96b8-3a5946246624"), FirstName = "Lautaro", MiddleName = "Javier", LastName = "Martínez", @@ -741,7 +753,7 @@ public static List GetSubstitutesWithId() Team = "Inter Milan", League = "Serie A", Starting11 = false, - } + }, ]; } @@ -848,8 +860,8 @@ public static List MakeStarting11FromDeserializedJson() "squadNumber": 24, "position": "Central Midfield", "abbrPosition": "CM", - "team": "Chelsea FC", - "league": "Premier League", + "team": "SL Benfica", + "league": "Liga Portugal", "starting11": true }, { @@ -860,7 +872,7 @@ public static List MakeStarting11FromDeserializedJson() "squadNumber": 20, "position": "Central Midfield", "abbrPosition": "CM", - "team": "Liverpool FC", + "team": "Brighton & Hove Albion", "league": "Premier League", "starting11": true }, @@ -872,8 +884,8 @@ public static List MakeStarting11FromDeserializedJson() "squadNumber": 10, "position": "Right Winger", "abbrPosition": "RW", - "team": "Inter Miami CF", - "league": "Major League Soccer", + "team": "Paris Saint-Germain", + "league": "Ligue 1", "starting11": true }, { @@ -900,6 +912,8 @@ public static List MakeStarting11FromDeserializedJson() return players; } - private static readonly JsonSerializerOptions options = - new() { PropertyNameCaseInsensitive = true }; + private static readonly JsonSerializerOptions options = new() + { + PropertyNameCaseInsensitive = true, + }; } diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db b/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db index 9d38e8c..b8899ac 100644 Binary files a/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db and b/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db differ diff --git a/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs b/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs index 5936824..72deea7 100644 --- a/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs +++ b/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Unit/PlayerControllerTests.cs @@ -486,8 +486,8 @@ public async Task Delete_PlayerBySquadNumber_NonExisting_Returns404NotFound() public async Task Delete_PlayerBySquadNumber_Existing_Returns204NoContent() { // Arrange - var squadNumber = 26; - var response = PlayerFakes.MakeResponseModelForUpdate(squadNumber); + var response = PlayerFakes.MakeResponseModelForCreate(); + var squadNumber = response.Dorsal; var (service, logger, validator) = PlayerMocks.InitControllerMocks(); service .Setup(service => service.RetrieveBySquadNumberAsync(squadNumber)) diff --git a/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs b/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs index 0059ba1..3d628af 100644 --- a/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs +++ b/test/Dotnet.Samples.AspNetCore.WebApi.Tests/Utilities/PlayerFakes.cs @@ -56,11 +56,11 @@ public static Player MakeFromStarting11(int squadNumber) /// public static Player MakeNew() { - // Get Leandro Paredes (squad number 5) from substitutes + // Get Giovani Lo Celso (squad number 27) from substitutes var player = - PlayerData.GetSubstitutes().SingleOrDefault(player => player.SquadNumber == 5) + PlayerData.GetSubstitutes().SingleOrDefault(player => player.SquadNumber == 27) ?? throw new InvalidOperationException( - "Substitute player with squad number 5 not found." + "Substitute player with squad number 27 not found." ); player.Id = Guid.NewGuid(); @@ -73,7 +73,7 @@ public static Player MakeNew() /// /// Creates a PlayerRequestModel for testing create operations. - /// Uses data from a substitute player (Leandro Paredes). + /// Uses data from a substitute player (Giovani Lo Celso). /// public static PlayerRequestModel MakeRequestModelForCreate() { @@ -88,13 +88,13 @@ public static PlayerRequestModel MakeRequestModelForCreate() SquadNumber = player.SquadNumber, AbbrPosition = player.AbbrPosition, Team = player.Team, - League = player.League + League = player.League, }; } /// /// Creates a PlayerResponseModel for testing create operation responses. - /// Uses data from a substitute player (Leandro Paredes). + /// Uses data from a substitute player (Giovani Lo Celso). /// public static PlayerResponseModel MakeResponseModelForCreate() { @@ -109,7 +109,7 @@ public static PlayerResponseModel MakeResponseModelForCreate() Position = player.Position, Club = player.Team, League = player.League, - Starting11 = player.Starting11 ? "Yes" : "No" + Starting11 = player.Starting11 ? "Yes" : "No", }; } @@ -139,7 +139,7 @@ public static PlayerRequestModel MakeRequestModelForRetrieve(int squadNumber) SquadNumber = player.SquadNumber, AbbrPosition = player.AbbrPosition, Team = player.Team, - League = player.League + League = player.League, }; } @@ -165,7 +165,7 @@ public static PlayerResponseModel MakeResponseModelForRetrieve(int squadNumber) Position = player.Position, Club = player.Team, League = player.League, - Starting11 = player.Starting11 ? "Yes" : "No" + Starting11 = player.Starting11 ? "Yes" : "No", }; } @@ -189,9 +189,9 @@ .. PlayerData Position = player.Position, Club = player.Team, League = player.League, - Starting11 = player.Starting11 ? "Yes" : "No" + Starting11 = player.Starting11 ? "Yes" : "No", }; - }) + }), ]; /* -------------------------------------------------------------------------