forked from leaderboardsgg/leaderboard-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Confirm Account Endpoint (leaderboardsgg#193)
* Remove redundant response type. * Implement ConfirmAccount. * Implement PUT /account/confirm/{id} endpoint. * Fix typo. * Add account confirm route to Consts.cs. * Include User navigation. * Add account confirmation tests. * Fix account confirmation tests. * dotnet format * Add XML comments. * Don't confirm if it JUST expired this instant. * Add results. * Update openapi.json. * Return 404 for malformed confirmation ID. * formatting * Be explicit about confirmation UsedAt time. * formatting * Split tests. * formatting
- Loading branch information
Showing
8 changed files
with
323 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
190 changes: 190 additions & 0 deletions
190
LeaderboardBackend.Test/Features/Users/ConfirmAccountTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using LeaderboardBackend.Models.Entities; | ||
using LeaderboardBackend.Test.Fixtures; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using NodaTime; | ||
using NodaTime.Testing; | ||
using NUnit.Framework; | ||
|
||
namespace LeaderboardBackend.Test.Features.Users; | ||
|
||
[TestFixture] | ||
public class ConfirmAccountTests : IntegrationTestsBase | ||
{ | ||
private IServiceScope _scope = null!; | ||
private readonly FakeClock _clock = new(Instant.FromUnixTimeSeconds(1)); | ||
private HttpClient _client = null!; | ||
|
||
[SetUp] | ||
public void Init() | ||
{ | ||
_scope = _factory.Services.CreateScope(); | ||
|
||
_client = _factory.WithWebHostBuilder(builder => | ||
{ | ||
builder.ConfigureTestServices(services => | ||
{ | ||
services.AddSingleton<IClock, FakeClock>(_ => _clock); | ||
}); | ||
}).CreateClient(); | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
_factory.ResetDatabase(); | ||
_scope.Dispose(); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_BadConfirmationId() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1)); | ||
ApplicationContext context = _scope.ServiceProvider.GetRequiredService<ApplicationContext>(); | ||
AccountConfirmation confirmation = new() | ||
{ | ||
CreatedAt = Instant.FromUnixTimeSeconds(0), | ||
ExpiresAt = Instant.FromUnixTimeSeconds(0).Plus(Duration.FromHours(1)), | ||
User = new() | ||
{ | ||
Email = "[email protected]", | ||
Password = "password", | ||
Username = "username", | ||
} | ||
}; | ||
|
||
await context.AccountConfirmations.AddAsync(confirmation); | ||
await context.SaveChangesAsync(); | ||
HttpResponseMessage res = await _client.PutAsync(Routes.ConfirmAccount(Guid.NewGuid()), null); | ||
res.StatusCode.Should().Be(HttpStatusCode.NotFound); | ||
context.ChangeTracker.Clear(); | ||
User? user = await context.Users.FindAsync(confirmation.UserId); | ||
user!.Role.Should().Be(UserRole.Registered); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_MalformedConfirmationId() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1)); | ||
HttpResponseMessage res = await _client.PutAsync("/account/confirm/not_a_guid", null); | ||
res.StatusCode.Should().Be(HttpStatusCode.NotFound); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_BadRole() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1)); | ||
ApplicationContext context = _scope.ServiceProvider.GetRequiredService<ApplicationContext>(); | ||
|
||
AccountConfirmation confirmation = new() | ||
{ | ||
CreatedAt = Instant.FromUnixTimeSeconds(0), | ||
ExpiresAt = Instant.FromUnixTimeSeconds(0).Plus(Duration.FromHours(1)), | ||
User = new() | ||
{ | ||
Email = "[email protected]", | ||
Password = "password", | ||
Username = "username", | ||
Role = UserRole.Confirmed | ||
} | ||
}; | ||
|
||
await context.AccountConfirmations.AddAsync(confirmation); | ||
await context.SaveChangesAsync(); | ||
|
||
HttpResponseMessage res = await _client.PutAsync(Routes.ConfirmAccount(confirmation.Id), null); | ||
res.StatusCode.Should().Be(HttpStatusCode.Conflict); | ||
context.ChangeTracker.Clear(); | ||
AccountConfirmation? conf = await context.AccountConfirmations.FindAsync(confirmation.Id); | ||
conf!.UsedAt.Should().BeNull(); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_Expired() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1) + Duration.FromHours(1)); | ||
ApplicationContext context = _scope.ServiceProvider.GetRequiredService<ApplicationContext>(); | ||
|
||
AccountConfirmation confirmation = new() | ||
{ | ||
CreatedAt = Instant.FromUnixTimeSeconds(0), | ||
ExpiresAt = Instant.FromUnixTimeSeconds(0).Plus(Duration.FromHours(1)), | ||
User = new() | ||
{ | ||
Email = "[email protected]", | ||
Password = "password", | ||
Username = "username", | ||
} | ||
}; | ||
|
||
await context.AccountConfirmations.AddAsync(confirmation); | ||
await context.SaveChangesAsync(); | ||
HttpResponseMessage res = await _client.PutAsync(Routes.ConfirmAccount(confirmation.Id), null); | ||
res.StatusCode.Should().Be(HttpStatusCode.NotFound); | ||
context.ChangeTracker.Clear(); | ||
AccountConfirmation? conf = await context.AccountConfirmations.Include(c => c.User).SingleOrDefaultAsync(c => c.Id == confirmation.Id); | ||
conf!.UsedAt.Should().BeNull(); | ||
conf!.User.Role.Should().Be(UserRole.Registered); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_AlreadyUsed() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1)); | ||
ApplicationContext context = _scope.ServiceProvider.GetRequiredService<ApplicationContext>(); | ||
|
||
AccountConfirmation confirmation = new() | ||
{ | ||
CreatedAt = Instant.FromUnixTimeSeconds(0), | ||
ExpiresAt = Instant.FromUnixTimeSeconds(0).Plus(Duration.FromHours(1)), | ||
UsedAt = Instant.FromUnixTimeSeconds(5), | ||
User = new() | ||
{ | ||
Email = "[email protected]", | ||
Password = "password", | ||
Username = "username", | ||
} | ||
}; | ||
|
||
await context.AccountConfirmations.AddAsync(confirmation); | ||
await context.SaveChangesAsync(); | ||
HttpResponseMessage res = await _client.PutAsync(Routes.ConfirmAccount(confirmation.Id), null); | ||
res.StatusCode.Should().Be(HttpStatusCode.NotFound); | ||
context.ChangeTracker.Clear(); | ||
User? user = await context.Users.FindAsync(confirmation.UserId); | ||
user!.Role.Should().Be(UserRole.Registered); | ||
} | ||
|
||
[Test] | ||
public async Task ConfirmAccount_Success() | ||
{ | ||
_clock.Reset(Instant.FromUnixTimeSeconds(1)); | ||
|
||
AccountConfirmation confirmation = new() | ||
{ | ||
CreatedAt = Instant.FromUnixTimeSeconds(0), | ||
ExpiresAt = Instant.FromUnixTimeSeconds(0).Plus(Duration.FromHours(1)), | ||
User = new() | ||
{ | ||
Email = "[email protected]", | ||
Password = "password", | ||
Username = "username", | ||
} | ||
}; | ||
|
||
ApplicationContext context = _scope.ServiceProvider.GetRequiredService<ApplicationContext>(); | ||
await context.AccountConfirmations.AddAsync(confirmation); | ||
await context.SaveChangesAsync(); | ||
HttpResponseMessage res = await _client.PutAsync(Routes.ConfirmAccount(confirmation.Id), null); | ||
res.Should().HaveStatusCode(HttpStatusCode.OK); | ||
context.ChangeTracker.Clear(); | ||
AccountConfirmation? conf = await context.AccountConfirmations.Include(c => c.User).SingleOrDefaultAsync(c => c.Id == confirmation.Id); | ||
conf!.UsedAt.Should().Be(Instant.FromUnixTimeSeconds(1)); | ||
conf!.User.Role.Should().Be(UserRole.Confirmed); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,11 @@ | ||
namespace LeaderboardBackend.Result; | ||
|
||
public readonly record struct AccountConfirmed(); | ||
public readonly record struct AlreadyUsed(); | ||
public readonly record struct BadCredentials(); | ||
public readonly record struct BadRole(); | ||
public readonly record struct ConfirmationNotFound(); | ||
public readonly record struct EmailFailed(); | ||
public readonly record struct Expired(); | ||
public readonly record struct UserNotFound(); | ||
public readonly record struct UserBanned(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.