From 881c758bae6f0b1e03a7af4efe49cc12ce351d6d Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Tue, 9 Jun 2026 10:51:18 -0500 Subject: [PATCH 1/2] feat: add tester deletion endpoint * Add DELETE /api/testers/{name} endpoint. * Remove testers and their related access history records. * Add integration tests for successful deletion, not found, unauthorized access, and cascade delete behavior. --- src/Endpoints/TesterEndpoints.cs | 9 ++ src/Extensions/ServiceCollectionExtensions.cs | 1 + src/UseCases/Testers/Delete.cs | 27 ++++++ tests/Common/DbContextHelpers.cs | 7 ++ tests/UseCases/Testers/Delete.cs | 88 +++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 src/UseCases/Testers/Delete.cs create mode 100644 tests/UseCases/Testers/Delete.cs diff --git a/src/Endpoints/TesterEndpoints.cs b/src/Endpoints/TesterEndpoints.cs index 0369684..6412110 100644 --- a/src/Endpoints/TesterEndpoints.cs +++ b/src/Endpoints/TesterEndpoints.cs @@ -34,6 +34,15 @@ public static void MapTesterEndpoints(this WebApplication app) }) .Produces>(); + testerGroup.MapDelete("/{name}", async ( + string name, + DeleteTesterUseCase useCase) => + { + var response = await useCase.ExecuteAsync(name); + return response.ToHttpResult(); + }) + .Produces>(); + testerGroup.MapPatch("/{accessKey}/playtime", async ( string accessKey, [FromBody]UpdatePlaytimeRequest request, diff --git a/src/Extensions/ServiceCollectionExtensions.cs b/src/Extensions/ServiceCollectionExtensions.cs index a3efcd9..ed8269d 100644 --- a/src/Extensions/ServiceCollectionExtensions.cs +++ b/src/Extensions/ServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ public static IServiceCollection AddServices(this IServiceCollection services) services .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/UseCases/Testers/Delete.cs b/src/UseCases/Testers/Delete.cs new file mode 100644 index 0000000..c209970 --- /dev/null +++ b/src/UseCases/Testers/Delete.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Playtesters.API.Data; +using Playtesters.API.Entities; +using SimpleResults; + +namespace Playtesters.API.UseCases.Testers; + +public record DeleteTesterResponse(string Name, string AccessKey); + +public class DeleteTesterUseCase(AppDbContext dbContext) +{ + public async Task> ExecuteAsync(string name) + { + var tester = await dbContext + .Set() + .FirstOrDefaultAsync(t => t.Name == name); + + if (tester is null) + return Result.NotFound(); + + dbContext.Remove(tester); + await dbContext.SaveChangesAsync(); + + var response = new DeleteTesterResponse(tester.Name, tester.AccessKey); + return Result.Success(response); + } +} diff --git a/tests/Common/DbContextHelpers.cs b/tests/Common/DbContextHelpers.cs index 448d9cf..44ff744 100644 --- a/tests/Common/DbContextHelpers.cs +++ b/tests/Common/DbContextHelpers.cs @@ -14,6 +14,13 @@ protected async Task FirstOrDefaultAsync( return await dbContext.Set().FirstOrDefaultAsync(predicate); } + protected int Count() where TEntity : class + { + using var scope = ApplicationFactory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetService(); + return dbContext.Set().Count(); + } + protected async Task> ToListAsync() where TEntity : class { using var scope = ApplicationFactory.Services.CreateScope(); diff --git a/tests/UseCases/Testers/Delete.cs b/tests/UseCases/Testers/Delete.cs new file mode 100644 index 0000000..1c01fb8 --- /dev/null +++ b/tests/UseCases/Testers/Delete.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Playtesters.API.Entities; +using Playtesters.API.Tests.Common; +using Playtesters.API.UseCases.Testers; +using SimpleResults; +using System.Net; + +namespace Playtesters.API.Tests.UseCases.Testers; + +public class DeleteTesterApiTests : TestBase +{ + [Test] + public async Task Delete_WhenTesterExists_ShouldDeleteTester() + { + // Arrange + var client = CreateHttpClientWithApiKey(); + + var createRequest = new CreateTesterRequest(Name: "Alice"); + await client.PostAsJsonAsync("/api/testers", createRequest); + + // Act + var response = await client.DeleteAsync("/api/testers/Alice"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var body = await response.Content.ReadFromJsonAsync>(); + + body.Should().NotBeNull(); + body.IsSuccess.Should().BeTrue(); + body.Data.Name.Should().Be("Alice"); + + var tester = await FirstOrDefaultAsync(t => t.Name == createRequest.Name); + tester.Should().BeNull(); + } + + [Test] + public async Task Delete_WhenTesterDoesNotExist_ShouldReturnNotFound() + { + // Arrange + var client = CreateHttpClientWithApiKey(); + + // Act + var response = await client.DeleteAsync("/api/testers/Unknown"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + + var body = await response.Content.ReadFromJsonAsync>(); + body.Should().NotBeNull(); + body.IsSuccess.Should().BeFalse(); + body.Status.Should().Be(ResultStatus.NotFound); + } + + [Test] + public async Task Delete_WhenTesterHasAccessHistory_ShouldDeleteAccessHistory() + { + // Arrange + var client = CreateHttpClientWithApiKey(); + var createRequest = new CreateTesterRequest(Name: "Alice"); + var createResponse = await client.PostAsJsonAsync("/api/testers", createRequest); + var createdTester = await createResponse.Content.ReadFromJsonAsync>(); + var validateRequest = new ValidateTesterAccessRequest(createdTester.Data.AccessKey); + await client.PostAsJsonAsync("/api/testers/validate-access", validateRequest); + Count().Should().Be(1); + + // Act + var response = await client.DeleteAsync("/api/testers/Alice"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + Count().Should().Be(0); + Count().Should().Be(0); + } + + [Test] + public async Task Delete_WhenMissingApiKey_ShouldReturnUnauthorized() + { + // Arrange + var client = ApplicationFactory.CreateClient(); + + // Act + var response = await client.DeleteAsync("/api/testers/Alice"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } +} From d01ed0f1448149fb42a57f2faba3333961f236ff Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Tue, 9 Jun 2026 10:54:16 -0500 Subject: [PATCH 2/2] chore: update .http file --- src/Playtesters.API.http | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Playtesters.API.http b/src/Playtesters.API.http index 2c50006..1f04067 100644 --- a/src/Playtesters.API.http +++ b/src/Playtesters.API.http @@ -47,6 +47,15 @@ X-Api-Key: {{adminKey}} ### +### --------------------------------------------------------- +### DELETE TESTER (by name) +### DELETE /api/testers/{name} +### --------------------------------------------------------- +DELETE {{host}}/api/testers/Tester123 +X-Api-Key: {{adminKey}} + +### + ### --------------------------------------------------------- ### LIST ALL TESTERS ### GET /api/testers