Skip to content

Commit

Permalink
Add conflict case
Browse files Browse the repository at this point in the history
  • Loading branch information
zysim committed Oct 19, 2024
1 parent 3679011 commit ac060b6
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 32 deletions.
39 changes: 16 additions & 23 deletions LeaderboardBackend.Test/Leaderboards.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using LeaderboardBackend.Models.Entities;
using LeaderboardBackend.Models.Requests;
Expand All @@ -15,7 +12,6 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualBasic;
using NodaTime;
using NodaTime.Testing;
using NUnit.Framework;
Expand Down Expand Up @@ -359,13 +355,10 @@ public async Task RestoreLeaderboard_OK()
Leaderboard deletedBoard = new()
{
Name = "Super Mario World",
Slug = "super-mario-world-deleted",
Slug = "super-mario-world-to-restore",
DeletedAt = _clock.GetCurrentInstant()
};

Instant now = Instant.FromUnixTimeSeconds(1);
_clock.Reset(now);

context.Leaderboards.Add(deletedBoard);
await context.SaveChangesAsync();
deletedBoard.Id.Should().NotBe(default);
Expand Down Expand Up @@ -394,21 +387,22 @@ public async Task RestoreLeaderboard_Unauthenticated()
await act.Should().ThrowAsync<RequestFailureException>().Where(e => e.Response.StatusCode == HttpStatusCode.Unauthorized);
}

[Test]
public async Task RestoreLeaderboard_Unauthorized()
[TestCase("[email protected]", "RestoreBoard1", UserRole.Confirmed)]
[TestCase("[email protected]", "RestoreBoard2", UserRole.Registered)]
public async Task RestoreLeaderboard_Unauthorized(string email, string username, UserRole role)
{
IUserService userService = _factory.Services.CreateScope().ServiceProvider.GetRequiredService<IUserService>();
UserViewModel userModel = await _apiClient.RegisterUser(username, email, "P4ssword");

RegisterRequest registerRequest = new()
{
Email = "[email protected]",
Password = "Passw0rd",
Username = "unauthorized"
};
ApplicationContext context = _factory.Services.CreateScope().ServiceProvider.GetRequiredService<ApplicationContext>();

await userService.CreateUser(registerRequest);
User? user = await context.Users.FindAsync([userModel.Id]);

context.Users.Update(user!);
user!.Role = role;

string jwt = (await _apiClient.LoginUser(registerRequest.Email, registerRequest.Password)).Token;
await context.SaveChangesAsync();

string jwt = (await _apiClient.LoginUser(email, "P4ssword")).Token;

Func<Task<LeaderboardViewModel>> act = async () => await _apiClient.Put<LeaderboardViewModel>($"/leaderboard/100/restore", new()
{
Expand All @@ -421,7 +415,7 @@ public async Task RestoreLeaderboard_Unauthorized()
[Test]
public async Task RestoreLeaderboard_NotFound()
{
Func<Task<LeaderboardViewModel>> act = async () => await _apiClient.Put<LeaderboardViewModel>($"/leaderboard/100/restore", new()
Func<Task<LeaderboardViewModel>> act = async () => await _apiClient.Put<LeaderboardViewModel>($"/leaderboard/{1e10}/restore", new()
{
Jwt = _jwt
});
Expand All @@ -436,8 +430,8 @@ public async Task RestoreLeaderboard_NotFound_WasNeverDeleted()

Leaderboard board = new()
{
Name = "Super Mario World",
Slug = "super-mario-world-non-deleted",
Name = "Hyper Mario World",
Slug = "hyper-mario-world-non-deleted",
};

context.Leaderboards.Add(board);
Expand All @@ -451,6 +445,5 @@ public async Task RestoreLeaderboard_NotFound_WasNeverDeleted()

await act.Should().ThrowAsync<RequestFailureException>()
.Where(e => e.Response.StatusCode == HttpStatusCode.NotFound);
// TODO: Don't know how to test for the response message.
}
}
10 changes: 6 additions & 4 deletions LeaderboardBackend/Controllers/LeaderboardsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ public async Task<ActionResult<LeaderboardViewModel>> CreateLeaderboard(
[Authorize(Policy = UserTypes.ADMINISTRATOR)]
[HttpPut("leaderboard/{id:long}/restore")]
[SwaggerOperation("Restores a deleted leaderboard.", OperationId = "restoreLeaderboard")]
[SwaggerResponse(200)]
[SwaggerResponse(200, "The restored `Leaderboard`s view model.", typeof(LeaderboardViewModel))]
[SwaggerResponse(401)]
[SwaggerResponse(403, "The requesting `User` is unauthorized to restore `Leaderboard`s.")]
[SwaggerResponse(404, "The `Leaderboard` was not found, or it wasn't deleted in the first place.")]
[SwaggerResponse(404, "The `Leaderboard` was not found, or it wasn't deleted in the first place.", typeof(string))]
[SwaggerResponse(409, "Another `Leaderboard` with the same slug has been created since, and therefore can't be restored.", typeof(LeaderboardViewModel))]
public async Task<ActionResult<LeaderboardViewModel>> RestoreLeaderboard(
long id
)
Expand All @@ -106,8 +107,9 @@ long id
neverDeleted =>
{
ModelState.AddModelError("Leaderboard", "LeaderboardWasNeverPreviouslyDeleted");
return NotFound(new ValidationProblemDetails(ModelState));
}
return NotFound("Was never deleted");
},
conflict => Conflict(conflict.Board)
);
}
}
3 changes: 3 additions & 0 deletions LeaderboardBackend/Results.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using LeaderboardBackend.Models.Entities;

namespace LeaderboardBackend.Result;

public readonly record struct AccountConfirmed;
Expand All @@ -10,5 +12,6 @@ namespace LeaderboardBackend.Result;
public readonly record struct CreateLeaderboardConflict;
public readonly record struct LeaderboardNotFound;
public readonly record struct LeaderboardNeverDeleted;
public readonly record struct RestoreLeaderboardConflict(Leaderboard Board);
public readonly record struct UserNotFound;
public readonly record struct UserBanned;
2 changes: 1 addition & 1 deletion LeaderboardBackend/Services/ILeaderboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ public interface ILeaderboardService
public partial class CreateLeaderboardResult : OneOfBase<Leaderboard, CreateLeaderboardConflict>;

[GenerateOneOf]
public partial class RestoreLeaderboardResult : OneOfBase<Leaderboard, LeaderboardNotFound, LeaderboardNeverDeleted>;
public partial class RestoreLeaderboardResult : OneOfBase<Leaderboard, LeaderboardNotFound, LeaderboardNeverDeleted, RestoreLeaderboardConflict>;
10 changes: 8 additions & 2 deletions LeaderboardBackend/Services/Impl/LeaderboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace LeaderboardBackend.Services;

public class LeaderboardService(ApplicationContext applicationContext, IClock clock) : ILeaderboardService
public class LeaderboardService(ApplicationContext applicationContext) : ILeaderboardService
{
public async Task<Leaderboard?> GetLeaderboard(long id) =>
await applicationContext.Leaderboards.FindAsync(id);
Expand Down Expand Up @@ -58,9 +58,15 @@ public async Task<RestoreLeaderboardResult> RestoreLeaderboard(long id)
return new LeaderboardNeverDeleted();
}

Leaderboard? maybe = await applicationContext.Leaderboards.SingleOrDefaultAsync(board => board.Slug == lb.Slug && board.DeletedAt == null);

if (maybe != null)
{
return new RestoreLeaderboardConflict(maybe);
}

applicationContext.Leaderboards.Update(lb);

lb.UpdatedAt = clock.GetCurrentInstant();
lb.DeletedAt = null;

await applicationContext.SaveChangesAsync();
Expand Down
21 changes: 19 additions & 2 deletions LeaderboardBackend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@
"description": "Internal Server Error"
},
"200": {
"description": "OK",
"description": "The restored `Leaderboard`s view model.",
"content": {
"application/json": {
"schema": {
Expand All @@ -713,7 +713,24 @@
"description": "The requesting `User` is unauthorized to restore `Leaderboard`s."
},
"404": {
"description": "The `Leaderboard` was not found, or it wasn't deleted in the first place."
"description": "The `Leaderboard` was not found, or it wasn't deleted in the first place.",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
},
"409": {
"description": "Another `Leaderboard` with the same slug has been created since, and therefore can't be restored.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LeaderboardViewModel"
}
}
}
}
}
}
Expand Down

0 comments on commit ac060b6

Please sign in to comment.