Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Endpoints/TesterEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ public static void MapTesterEndpoints(this WebApplication app)
})
.Produces<Result<UpdateTesterResponse>>();

testerGroup.MapDelete("/{name}", async (
string name,
DeleteTesterUseCase useCase) =>
{
var response = await useCase.ExecuteAsync(name);
return response.ToHttpResult();
})
.Produces<Result<DeleteTesterResponse>>();

testerGroup.MapPatch("/{accessKey}/playtime", async (
string accessKey,
[FromBody]UpdatePlaytimeRequest request,
Expand Down
1 change: 1 addition & 0 deletions src/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static IServiceCollection AddServices(this IServiceCollection services)
services
.AddScoped<CreateTesterUseCase>()
.AddScoped<UpdateTesterUseCase>()
.AddScoped<DeleteTesterUseCase>()
.AddScoped<UpdatePlaytimeUseCase>()
.AddScoped<GetTestersUseCase>()
.AddScoped<ValidateTesterAccessUseCase>()
Expand Down
9 changes: 9 additions & 0 deletions src/Playtesters.API.http
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions src/UseCases/Testers/Delete.cs
Original file line number Diff line number Diff line change
@@ -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<Result<DeleteTesterResponse>> ExecuteAsync(string name)
{
var tester = await dbContext
.Set<Tester>()
.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);
}
}
7 changes: 7 additions & 0 deletions tests/Common/DbContextHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ protected async Task<TEntity> FirstOrDefaultAsync<TEntity>(
return await dbContext.Set<TEntity>().FirstOrDefaultAsync(predicate);
}

protected int Count<TEntity>() where TEntity : class
{
using var scope = ApplicationFactory.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetService<AppDbContext>();
return dbContext.Set<TEntity>().Count();
}

protected async Task<List<TEntity>> ToListAsync<TEntity>() where TEntity : class
{
using var scope = ApplicationFactory.Services.CreateScope();
Expand Down
88 changes: 88 additions & 0 deletions tests/UseCases/Testers/Delete.cs
Original file line number Diff line number Diff line change
@@ -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<Result<DeleteTesterResponse>>();

body.Should().NotBeNull();
body.IsSuccess.Should().BeTrue();
body.Data.Name.Should().Be("Alice");

var tester = await FirstOrDefaultAsync<Tester>(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<Result<DeleteTesterResponse>>();
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<Result<CreateTesterResponse>>();
var validateRequest = new ValidateTesterAccessRequest(createdTester.Data.AccessKey);
await client.PostAsJsonAsync("/api/testers/validate-access", validateRequest);
Count<AccessValidationHistory>().Should().Be(1);

// Act
var response = await client.DeleteAsync("/api/testers/Alice");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
Count<Tester>().Should().Be(0);
Count<AccessValidationHistory>().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);
}
}