Files
ColaFlow/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Identity/RbacTests.cs
Yaojia Wang a220e5d5d7
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Refactor
2025-11-03 21:02:14 +01:00

230 lines
8.6 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
using FluentAssertions;
namespace ColaFlow.Modules.Identity.IntegrationTests.Identity;
/// <summary>
/// Integration tests for Role-Based Access Control (RBAC) functionality (Day 5 - Phase 2)
/// Tests role assignment, JWT claims, and role persistence across authentication flows
/// </summary>
public class RbacTests(DatabaseFixture fixture) : IClassFixture<DatabaseFixture>
{
private readonly HttpClient _client = fixture.Client;
[Fact]
public async Task RegisterTenant_ShouldAssignTenantOwnerRole()
{
// Arrange & Act
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Assert - Verify token contains TenantOwner role
TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
}
[Fact]
public async Task RegisterTenant_ShouldIncludeRoleInJwtClaims()
{
// Arrange & Act
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Assert - Decode JWT and verify claims
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(accessToken);
var claims = token.Claims.ToList();
// Should have either 'role' or 'tenant_role' claim with value 'TenantOwner'
claims.Should().Contain(c =>
(c.Type == "role" || c.Type == "tenant_role") &&
c.Value == "TenantOwner");
}
[Fact]
public async Task Login_ShouldPreserveRole()
{
// Arrange - Register tenant
var email = $"admin-{Guid.NewGuid():N}@test.com";
var tenantSlug = $"test-{Guid.NewGuid():N}";
var password = "Admin@1234";
await RegisterTenantAsync(tenantSlug, email, password);
// Act - Login
var (accessToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
// Assert - Role should be preserved
TestAuthHelper.HasRole(accessToken, "TenantOwner").Should().BeTrue();
}
[Fact]
public async Task RefreshToken_ShouldPreserveRole()
{
// Arrange - Register and get initial tokens
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Act - Refresh token
var refreshRequest = new { refreshToken };
var response = await _client.PostAsJsonAsync("/api/auth/refresh", refreshRequest);
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
// Assert - New token should preserve role
TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();
}
[Fact]
public async Task GetMe_ShouldReturnUserRoleInformation()
{
// Arrange - Register and get tokens
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Act - Call /api/auth/me with token
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _client.GetAsync("/api/auth/me");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var userInfo = await response.Content.ReadFromJsonAsync<UserInfoResponse>();
userInfo.Should().NotBeNull();
userInfo!.TenantRole.Should().Be("TenantOwner");
}
[Fact]
public async Task JwtToken_ShouldContainAllRequiredRoleClaims()
{
// Arrange & Act
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Assert - Verify all expected claims
var claims = TestAuthHelper.ParseJwtToken(accessToken).ToList();
// Must have user identity claims
claims.Should().Contain(c => c.Type == "user_id");
claims.Should().Contain(c => c.Type == "tenant_id");
claims.Should().Contain(c => c.Type == "email");
claims.Should().Contain(c => c.Type == "full_name");
// Must have role claim
claims.Should().Contain(c =>
(c.Type == "role" || c.Type == "tenant_role") &&
c.Value == "TenantOwner");
}
[Fact]
public async Task MultipleTokenRefresh_ShouldMaintainRole()
{
// Arrange - Register and get initial tokens
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Act & Assert - Refresh multiple times
for (int i = 0; i < 3; i++)
{
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
// Verify role is maintained
TestAuthHelper.HasRole(result!.AccessToken, "TenantOwner").Should().BeTrue();
// Update token for next iteration
refreshToken = result.RefreshToken;
}
}
[Fact]
public async Task AccessProtectedEndpoint_WithValidRole_ShouldSucceed()
{
// Arrange - Register and get token with TenantOwner role
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
// Act - Access protected endpoint
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _client.GetAsync("/api/auth/me");
// Assert - Should succeed because user has valid role
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
[Fact]
public async Task AccessProtectedEndpoint_WithoutToken_ShouldFail()
{
// Arrange - No authorization header
// Act - Try to access protected endpoint
var response = await _client.GetAsync("/api/auth/me");
// Assert - Should fail with 401 Unauthorized
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task AccessProtectedEndpoint_WithInvalidToken_ShouldFail()
{
// Arrange - Invalid token
var invalidToken = "invalid.jwt.token";
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", invalidToken);
// Act - Try to access protected endpoint
var response = await _client.GetAsync("/api/auth/me");
// Assert - Should fail with 401 Unauthorized
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task RoleInformation_ShouldBeConsistentAcrossAllFlows()
{
// This test verifies role consistency across:
// 1. Registration
// 2. Login
// 3. Token Refresh
// 4. User Info Endpoint
var email = $"admin-{Guid.NewGuid():N}@test.com";
var tenantSlug = $"test-{Guid.NewGuid():N}";
var password = "Admin@1234";
// Step 1: Register
await RegisterTenantAsync(tenantSlug, email, password);
var (registerToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
TestAuthHelper.HasRole(registerToken, "TenantOwner").Should().BeTrue("Registration should assign TenantOwner");
// Step 2: Login
var (loginToken, refreshToken) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
TestAuthHelper.HasRole(loginToken, "TenantOwner").Should().BeTrue("Login should preserve TenantOwner");
// Step 3: Token Refresh
var refreshResponse = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
var refreshResult = await refreshResponse.Content.ReadFromJsonAsync<RefreshResponse>();
TestAuthHelper.HasRole(refreshResult!.AccessToken, "TenantOwner").Should().BeTrue("Refresh should preserve TenantOwner");
// Step 4: User Info Endpoint
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshResult.AccessToken);
var meResponse = await _client.GetAsync("/api/auth/me");
var userInfo = await meResponse.Content.ReadFromJsonAsync<UserInfoResponse>();
userInfo!.TenantRole.Should().Be("TenantOwner", "User info should show TenantOwner");
}
#region Helper Methods
private async Task RegisterTenantAsync(string tenantSlug, string email, string password)
{
var request = new
{
tenantName = "Test Corp",
tenantSlug,
subscriptionPlan = "Professional",
adminEmail = email,
adminPassword = password,
adminFullName = "Test Admin"
};
var response = await _client.PostAsJsonAsync("/api/tenants/register", request);
response.EnsureSuccessStatusCode();
}
#endregion
}