152 lines
5.4 KiB
C#
152 lines
5.4 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
/// <summary>
|
|
/// Integration tests for authentication flow: login, refresh, logout, and role-based access.
|
|
/// </summary>
|
|
[Collection("Database")]
|
|
public class AuthTests : IAsyncLifetime
|
|
{
|
|
private readonly ApiFixture _fixture;
|
|
|
|
public AuthTests(ApiFixture fixture) => _fixture = fixture;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
using var scope = _fixture.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
await DbResetHelper.ResetAsync(db);
|
|
await DataSeeder.SeedAsync(db);
|
|
}
|
|
|
|
public Task DisposeAsync() => Task.CompletedTask;
|
|
|
|
[Fact]
|
|
public async Task Login_ValidCredentials_ReturnsTokenAndProfile()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
var response = await client.PostAsJsonAsync("/api/v1/auth/login",
|
|
new { username = "entry1", password = "password" });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
var data = body.GetProperty("data");
|
|
data.GetProperty("token").GetString().Should().NotBeNullOrEmpty();
|
|
data.GetProperty("refreshToken").GetString().Should().NotBeNullOrEmpty();
|
|
data.GetProperty("username").GetString().Should().Be("entry1");
|
|
data.GetProperty("role").GetString().Should().Be("DATA_ENTRY_CLERK");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_InvalidPassword_Returns422()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
var response = await client.PostAsJsonAsync("/api/v1/auth/login",
|
|
new { username = "entry1", password = "wrongpassword" });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_NonexistentUser_Returns422()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
var response = await client.PostAsJsonAsync("/api/v1/auth/login",
|
|
new { username = "nonexistent", password = "password" });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProtectedEndpoint_NoToken_Returns401()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/v1/digitization-batches");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RoleRestricted_WrongRole_Returns403()
|
|
{
|
|
// entry1 is DATA_ENTRY_CLERK — cannot access admin dashboard
|
|
var client = await AuthHelper.LoginAsync(_fixture, "entry1");
|
|
|
|
var response = await client.GetAsync("/api/v1/work-queue/overview");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Refresh_ValidToken_ReturnsNewTokens()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
// Login to get refresh token
|
|
var loginResponse = await client.PostAsJsonAsync("/api/v1/auth/login",
|
|
new { username = "verifier1", password = "password" });
|
|
loginResponse.EnsureSuccessStatusCode();
|
|
|
|
var loginBody = await loginResponse.Content.ReadFromJsonAsync<JsonElement>();
|
|
var refreshToken = loginBody.GetProperty("data").GetProperty("refreshToken").GetString();
|
|
|
|
// Refresh
|
|
var refreshResponse = await client.PostAsJsonAsync("/api/v1/auth/refresh",
|
|
new { refreshToken });
|
|
|
|
refreshResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var refreshBody = await refreshResponse.Content.ReadFromJsonAsync<JsonElement>();
|
|
refreshBody.GetProperty("data").GetProperty("token").GetString().Should().NotBeNullOrEmpty();
|
|
refreshBody.GetProperty("data").GetProperty("refreshToken").GetString()
|
|
.Should().NotBe(refreshToken, "refresh should rotate the token");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Logout_RevokesRefreshToken()
|
|
{
|
|
var client = _fixture.CreateClient();
|
|
|
|
// Login
|
|
var loginResponse = await client.PostAsJsonAsync("/api/v1/auth/login",
|
|
new { username = "intake1", password = "password" });
|
|
loginResponse.EnsureSuccessStatusCode();
|
|
|
|
var loginBody = await loginResponse.Content.ReadFromJsonAsync<JsonElement>();
|
|
var refreshToken = loginBody.GetProperty("data").GetProperty("refreshToken").GetString();
|
|
|
|
// Logout
|
|
var logoutResponse = await client.PostAsJsonAsync("/api/v1/auth/logout",
|
|
new { refreshToken });
|
|
logoutResponse.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
|
|
|
// Attempt to refresh with revoked token
|
|
var refreshResponse = await client.PostAsJsonAsync("/api/v1/auth/refresh",
|
|
new { refreshToken });
|
|
refreshResponse.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Me_Authenticated_ReturnsProfile()
|
|
{
|
|
var client = await AuthHelper.LoginAsync(_fixture, "approver1");
|
|
|
|
var response = await client.GetAsync("/api/v1/auth/me");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
var data = body.GetProperty("data");
|
|
data.GetProperty("username").GetString().Should().Be("approver1");
|
|
data.GetProperty("role").GetString().Should().Be("CLINICAL_APPROVER");
|
|
}
|
|
}
|