diff --git a/Dotnet.AspNetCore.Samples.WebApi.Tests/Dotnet.AspNetCore.Samples.WebApi.Tests.csproj b/Dotnet.AspNetCore.Samples.WebApi.Tests/Dotnet.AspNetCore.Samples.WebApi.Tests.csproj index 0f3b4a6..4ebd07b 100644 --- a/Dotnet.AspNetCore.Samples.WebApi.Tests/Dotnet.AspNetCore.Samples.WebApi.Tests.csproj +++ b/Dotnet.AspNetCore.Samples.WebApi.Tests/Dotnet.AspNetCore.Samples.WebApi.Tests.csproj @@ -12,7 +12,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,8 +23,8 @@ - - + + diff --git a/Dotnet.AspNetCore.Samples.WebApi.Tests/PlayerServiceTests.cs b/Dotnet.AspNetCore.Samples.WebApi.Tests/PlayerServiceTests.cs index f71afd8..f2e05fd 100644 --- a/Dotnet.AspNetCore.Samples.WebApi.Tests/PlayerServiceTests.cs +++ b/Dotnet.AspNetCore.Samples.WebApi.Tests/PlayerServiceTests.cs @@ -1,28 +1,94 @@ +using System.Data.Common; using System.Diagnostics; using Dotnet.AspNetCore.Samples.WebApi.Data; using Dotnet.AspNetCore.Samples.WebApi.Models; using Dotnet.AspNetCore.Samples.WebApi.Services; using FluentAssertions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Moq; -using Moq.EntityFrameworkCore; namespace Dotnet.AspNetCore.Samples.WebApi.Tests; -public class PlayerServiceTests +public class PlayerServiceTests : IDisposable { + private readonly DbConnection dbConnection; + private readonly DbContextOptions dbContextOptions; + + public PlayerServiceTests() + { + dbConnection = new SqliteConnection("Filename=:memory:"); + dbConnection.Open(); + + dbContextOptions = new DbContextOptionsBuilder() + .UseSqlite(dbConnection) + .Options; + + using var context = new PlayerContext(dbContextOptions); + + if (context.Database.EnsureCreated()) + { + using var dbCommand = context.Database.GetDbConnection().CreateCommand(); + + dbCommand.CommandText = """ + CREATE TABLE IF NOT EXISTS "players" + ( + "id" INTEGER, + "firstName" TEXT NOT NULL, + "middleName" TEXT, + "lastName" TEXT NOT NULL, + "dateOfBirth" TEXT, + "squadNumber" INTEGER NOT NULL, + "position" TEXT NOT NULL, + "abbrPosition" TEXT, + "team" TEXT, + "league" TEXT, + "starting11" BOOLEAN, + PRIMARY KEY("id") + ); + """; + + dbCommand.ExecuteNonQuery(); + } + + context.AddRange(PlayerDataBuilder.SeedWithDeserializedJson()); + context.SaveChanges(); + } + + PlayerContext CreatePlayerContext() => new PlayerContext(dbContextOptions); + public void Dispose() => dbConnection.Dispose(); + [Fact] [Trait("Category", "Retrieve")] - public async Task GivenRetrieve_WhenInvokedTwice_ThenSecondExecutionTimeShouldBeLessThanFirst() + public async Task GivenRetrieveAsync_WhenInvoked_ThenShouldReturnAllPlayers() { // Arrange - var players = PlayerDataBuilder.SeedWithStarting11().ToList(); - var context = new Mock(); - context.Setup(context => context.Players).ReturnsDbSet(players); + var players = PlayerDataBuilder.SeedWithDeserializedJson(); + var context = CreatePlayerContext(); var logger = new Mock>(); var memoryCache = new MemoryCache(new MemoryCacheOptions()); - var service = new PlayerService(context.Object, logger.Object, memoryCache); + + var service = new PlayerService(context, logger.Object, memoryCache); + + // Act + var result = await service.RetrieveAsync(); + + // Assert + result.Should().BeEquivalentTo(players); + } + + [Fact] + [Trait("Category", "Retrieve")] + public async Task GivenRetrieveAsync_WhenInvokedTwice_ThenSecondExecutionTimeShouldBeLessThanFirst() + { + // Arrange + var context = CreatePlayerContext(); + var logger = new Mock>(); + var memoryCache = new MemoryCache(new MemoryCacheOptions()); + + var service = new PlayerService(context, logger.Object, memoryCache); // Act var first = await ExecutionTimeAsync(() => service.RetrieveAsync()); @@ -41,4 +107,24 @@ private async Task ExecutionTimeAsync(Func awaitable) return stopwatch.ElapsedMilliseconds; } + + [Fact] + [Trait("Category", "Retrieve")] + public async Task GivenRetrieveByIdAsync_WhenInvokedWithPlayerId_ThenShouldReturnThePlayer() + { + // Arrange + var player = PlayerDataBuilder.SeedOneById(10); + var context = CreatePlayerContext(); + var logger = new Mock>(); + var memoryCache = new MemoryCache(new MemoryCacheOptions()); + + var service = new PlayerService(context, logger.Object, memoryCache); + + // Act + var result = await service.RetrieveByIdAsync(10); + + // Assert + result.Should().BeOfType(); + result.Should().BeEquivalentTo(player); + } } diff --git a/Dotnet.AspNetCore.Samples.WebApi/Controllers/PlayersController.cs b/Dotnet.AspNetCore.Samples.WebApi/Controllers/PlayersController.cs index ec738a9..7d80a19 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Controllers/PlayersController.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Controllers/PlayersController.cs @@ -38,7 +38,7 @@ HTTP POST [HttpPost] public async Task> PostPlayer(Player player) { - if (await _playerService.RetrieveById(player.Id) != null) + if (await _playerService.RetrieveByIdAsync(player.Id) != null) { return Conflict(); } @@ -74,7 +74,7 @@ public async Task>> GetPlayers() [HttpGet("{id}")] public async Task> GetPlayer(long id) { - var player = await _playerService.RetrieveById(id); + var player = await _playerService.RetrieveByIdAsync(id); if (player != null) { @@ -100,7 +100,7 @@ public async Task PutPlayer(long id, Player player) { return BadRequest(); } - else if (await _playerService.RetrieveById(id) == null) + else if (await _playerService.RetrieveByIdAsync(id) == null) { return NotFound(); } @@ -121,7 +121,7 @@ HTTP DELETE [HttpDelete("{id}")] public async Task DeletePlayer(long id) { - if (await _playerService.RetrieveById(id) == null) + if (await _playerService.RetrieveByIdAsync(id) == null) { return NotFound(); } diff --git a/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerContextInitializer.cs b/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerContextInitializer.cs index e493168..8422195 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerContextInitializer.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerContextInitializer.cs @@ -1,5 +1,6 @@ using Dotnet.AspNetCore.Samples.WebApi.Data; using Dotnet.AspNetCore.Samples.WebApi.Models; +using Microsoft.EntityFrameworkCore; namespace Dotnet.AspNetCore.Samples.WebApi; @@ -10,9 +11,10 @@ public static void Seed(IApplicationBuilder applicationBuilder) using (var scope = applicationBuilder.ApplicationServices.CreateScope()) { var context = scope.ServiceProvider.GetService(); - + if (context != null) { + // https://learn.microsoft.com/en-us/ef/core/managing-schemas/ensure-created context.Database.EnsureCreated(); if (!context.Players.Any()) diff --git a/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerDataBuilder.cs b/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerDataBuilder.cs index f70ab4b..e698167 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerDataBuilder.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Data/PlayerDataBuilder.cs @@ -5,7 +5,203 @@ namespace Dotnet.AspNetCore.Samples.WebApi.Data; public static class PlayerDataBuilder { - public static Player[] SeedWithStarting11() + public static Player? SeedOneById(int id) + { + return SeedWithStarting11() + .SingleOrDefault(player => player.Id == id); + } + + public static List SeedWithStarting11() + { + var players = new List(); + + players.Add( + new Player + { + Id = 1, + FirstName = "Damián", + MiddleName = "Emiliano", + LastName = "Martínez", + DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 23, + Position = "Goalkeeper", + AbbrPosition = "GK", + Team = "Aston Villa FC", + League = "Premier League", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 2, + FirstName = "Nahuel", + LastName = "Molina", + DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 26, + Position = "Right-Back", + AbbrPosition = "RB", + Team = "Altético Madrid", + League = "La Liga", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 3, + FirstName = "Cristian", + MiddleName = "Gabriel", + LastName = "Romero", + DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 13, + Position = "Centre-Back", + AbbrPosition = "CB", + Team = "Tottenham Hotspur", + League = "Premier League", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 4, + FirstName = "Nicolás", + MiddleName = "Hernán Gonzalo", + LastName = "Otamendi", + DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 19, + Position = "Centre-Back", + AbbrPosition = "CB", + Team = "SL Benfica", + League = "Liga Portugal", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 5, + FirstName = "Nicolás", + MiddleName = "Alejandro", + LastName = "Tagliafico", + DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 3, + Position = "Left-Back", + AbbrPosition = "LB", + Team = "Olympique Lyon", + League = "Ligue 1", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 6, + FirstName = "Ángel", + MiddleName = "Fabián", + LastName = "Di María", + DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 11, + Position = "Right Winger", + AbbrPosition = "RW", + Team = "SL Benfica", + League = "Liga Portugal", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 7, + FirstName = "Rodrigo", + MiddleName = "Javier", + LastName = "de Paul", + DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 7, + Position = "Central Midfield", + AbbrPosition = "CM", + Team = "Altético Madrid", + League = "La Liga", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 8, + FirstName = "Enzo", + MiddleName = "Jeremías", + LastName = "Fernández", + DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 24, + Position = "Central Midfield", + AbbrPosition = "CM", + Team = "Chelsea FC", + League = "Premier League", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 9, + FirstName = "Alexis", + LastName = "Mac Allister", + DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 20, + Position = "Central Midfield", + AbbrPosition = "CM", + Team = "Liverpool FC", + League = "Premier League", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 10, + FirstName = "Lionel", + MiddleName = "Andrés", + LastName = "Messi", + DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 10, + Position = "Right Winger", + AbbrPosition = "RW", + Team = "Inter Miami CF", + League = "Major League Soccer", + Starting11 = true, + } + ); + + players.Add( + new Player + { + Id = 11, + FirstName = "Julián", + LastName = "Álvarez", + DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 9, + Position = "Centre-Forward", + AbbrPosition = "CF", + Team = "Manchester City", + League = "Premier League", + Starting11 = true, + } + ); + + return players; + } + public static List SeedWithDeserializedJson() { var players = new List(); @@ -15,143 +211,143 @@ public static Player[] SeedWithStarting11() "firstName": "Damián", "middleName": "Emiliano", "lastName": "Martínez", - "dateOfBirth": "1992-09-01T21:00:00-00:00", + "dateOfBirth": "1992-09-01T00:00:00.000Z", "squadNumber": 23, "position": "Goalkeeper", "abbrPosition": "GK", "team": "Aston Villa FC", "league": "Premier League", - "starting11": false + "starting11": true }, { "id": 2, "firstName": "Nahuel", "middleName": null, "lastName": "Molina", - "dateOfBirth": "1998-04-05T21:00:00-00:00", + "dateOfBirth": "1998-04-05T00:00:00.000Z", "squadNumber": 26, "position": "Right-Back", "abbrPosition": "RB", "team": "Atlético Madrid", "league": "La Liga", - "starting11": false + "starting11": true }, { "id": 3, "firstName": "Cristian", "middleName": "Gabriel", "lastName": "Romero", - "dateOfBirth": "1998-04-26T21:00:00-00:00", + "dateOfBirth": "1998-04-26T00:00:00.000Z", "squadNumber": 13, "position": "Centre-Back", "abbrPosition": "CB", "team": "Tottenham Hotspur", "league": "Premier League", - "starting11": false + "starting11": true }, { "id": 4, "firstName": "Nicolás", "middleName": "Hernán Gonzalo", "lastName": "Otamendi", - "dateOfBirth": "1988-02-11T21:00:00-00:00", + "dateOfBirth": "1988-02-11T00:00:00.000Z", "squadNumber": 19, "position": "Centre-Back", "abbrPosition": "CB", "team": "SL Benfica", "league": "Liga Portugal", - "starting11": false + "starting11": true }, { "id": 5, "firstName": "Nicolás", "middleName": "Alejandro", "lastName": "Tagliafico", - "dateOfBirth": "1992-08-30T21:00:00-00:00", + "dateOfBirth": "1992-08-30T00:00:00.000Z", "squadNumber": 3, "position": "Left-Back", "abbrPosition": "LB", "team": "Olympique Lyon", "league": "Ligue 1", - "starting11": false + "starting11": true }, { "id": 6, "firstName": "Ángel", "middleName": "Fabián", "lastName": "Di María", - "dateOfBirth": "1988-02-13T21:00:00-00:00", + "dateOfBirth": "1988-02-13T00:00:00.000Z", "squadNumber": 11, "position": "Right Winger", - "abbrPosition": "LW", + "abbrPosition": "RW", "team": "SL Benfica", "league": "Liga Portugal", - "starting11": false + "starting11": true }, { "id": 7, "firstName": "Rodrigo", "middleName": "Javier", "lastName": "de Paul", - "dateOfBirth": "1994-05-23T21:00:00-00:00", + "dateOfBirth": "1994-05-23T00:00:00.000Z", "squadNumber": 7, "position": "Central Midfield", "abbrPosition": "CM", "team": "Atlético Madrid", "league": "La Liga", - "starting11": false + "starting11": true }, { "id": 8, "firstName": "Enzo", "middleName": "Jeremías", "lastName": "Fernández", - "dateOfBirth": "2001-01-16T21:00:00-00:00", + "dateOfBirth": "2001-01-16T00:00:00.000Z", "squadNumber": 24, "position": "Central Midfield", "abbrPosition": "CM", "team": "Chelsea FC", "league": "Premier League", - "starting11": false + "starting11": true }, { "id": 9, "firstName": "Alexis", "middleName": null, "lastName": "Mac Allister", - "dateOfBirth": "1998-12-23T21:00:00-00:00", + "dateOfBirth": "1998-12-23T00:00:00.000Z", "squadNumber": 20, "position": "Central Midfield", "abbrPosition": "CM", "team": "Liverpool FC", "league": "Premier League", - "starting11": false + "starting11": true }, { "id": 10, "firstName": "Lionel", "middleName": "Andrés", "lastName": "Messi", - "dateOfBirth": "1987-06-23T21:00:00-00:00", + "dateOfBirth": "1987-06-23T00:00:00.000Z", "squadNumber": 10, "position": "Right Winger", "abbrPosition": "RW", "team": "Inter Miami CF", "league": "Major League Soccer", - "starting11": false + "starting11": true }, { "id": 11, "firstName": "Julián", "middleName": null, "lastName": "Álvarez", - "dateOfBirth": "2000-01-30T21:00:00-00:00", + "dateOfBirth": "2000-01-30T00:00:00.000Z", "squadNumber": 9, "position": "Centre-Forward", "abbrPosition": "CF", "team": "Manchester City", "league": "Premier League", - "starting11": false + "starting11": true }] """; @@ -163,6 +359,6 @@ public static Player[] SeedWithStarting11() players.AddRange(starting11); } - return players.ToArray(); + return players; } } diff --git a/Dotnet.AspNetCore.Samples.WebApi/Program.cs b/Dotnet.AspNetCore.Samples.WebApi/Program.cs index 5e95af7..e4cd65c 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Program.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Program.cs @@ -1,6 +1,7 @@ using Dotnet.AspNetCore.Samples.WebApi; using Dotnet.AspNetCore.Samples.WebApi.Models; using Dotnet.AspNetCore.Samples.WebApi.Services; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -13,13 +14,8 @@ builder.Services.AddControllers(); -// var localApplicationData = Environment.SpecialFolder.LocalApplicationData; -// var folderPath = Environment.GetFolderPath(localApplicationData); -var projectDataFolder = "Data"; -var path = Path.Join(projectDataFolder, "players-sqlite3.db"); - builder.Services.AddDbContext(options => - options.UseSqlite($"Data Source={path}") + options.UseSqlite(@"Data Source=Data/players-sqlite3.db") ); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/Dotnet.AspNetCore.Samples.WebApi/Services/IPlayerService.cs b/Dotnet.AspNetCore.Samples.WebApi/Services/IPlayerService.cs index 96d32dd..3774c55 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Services/IPlayerService.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Services/IPlayerService.cs @@ -9,7 +9,7 @@ public interface IPlayerService public Task> RetrieveAsync(); - public ValueTask RetrieveById(long id); + public ValueTask RetrieveByIdAsync(long id); public Task UpdateAsync(Player player); diff --git a/Dotnet.AspNetCore.Samples.WebApi/Services/PlayerService.cs b/Dotnet.AspNetCore.Samples.WebApi/Services/PlayerService.cs index 12e82b2..deaf7b1 100644 --- a/Dotnet.AspNetCore.Samples.WebApi/Services/PlayerService.cs +++ b/Dotnet.AspNetCore.Samples.WebApi/Services/PlayerService.cs @@ -57,7 +57,7 @@ public async Task> RetrieveAsync() } } - public ValueTask RetrieveById(long id) + public ValueTask RetrieveByIdAsync(long id) { return _playerContext.Players.FindAsync(id); }