Commit all scripts
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
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 basic Authentication functionality (Day 4 Regression Tests)
|
||||
/// Tests registration, login, password validation, and protected endpoints
|
||||
/// </summary>
|
||||
public class AuthenticationTests : IClassFixture<DatabaseFixture>
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public AuthenticationTests(DatabaseFixture fixture)
|
||||
{
|
||||
_client = fixture.Client;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterTenant_WithValidData_ShouldSucceed()
|
||||
{
|
||||
// Arrange
|
||||
var request = new
|
||||
{
|
||||
tenantName = "Test Corp",
|
||||
tenantSlug = $"test-{Guid.NewGuid():N}",
|
||||
subscriptionPlan = "Professional",
|
||||
adminEmail = $"admin-{Guid.NewGuid():N}@test.com",
|
||||
adminPassword = "Admin@1234",
|
||||
adminFullName = "Test Admin"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/tenants/register", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<RegisterResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.AccessToken.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterTenant_WithDuplicateSlug_ShouldFail()
|
||||
{
|
||||
// Arrange - Register first tenant
|
||||
var slug = $"test-{Guid.NewGuid():N}";
|
||||
var firstRequest = new
|
||||
{
|
||||
tenantName = "First Corp",
|
||||
tenantSlug = slug,
|
||||
subscriptionPlan = "Professional",
|
||||
adminEmail = $"admin1-{Guid.NewGuid():N}@test.com",
|
||||
adminPassword = "Admin@1234",
|
||||
adminFullName = "First Admin"
|
||||
};
|
||||
await _client.PostAsJsonAsync("/api/tenants/register", firstRequest);
|
||||
|
||||
// Act - Try to register with same slug
|
||||
var secondRequest = new
|
||||
{
|
||||
tenantName = "Second Corp",
|
||||
tenantSlug = slug,
|
||||
subscriptionPlan = "Professional",
|
||||
adminEmail = $"admin2-{Guid.NewGuid():N}@test.com",
|
||||
adminPassword = "Admin@1234",
|
||||
adminFullName = "Second Admin"
|
||||
};
|
||||
var response = await _client.PostAsJsonAsync("/api/tenants/register", secondRequest);
|
||||
|
||||
// Assert - Should fail with conflict or bad request
|
||||
response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.Conflict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithCorrectCredentials_ShouldSucceed()
|
||||
{
|
||||
// Arrange - Register tenant
|
||||
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
||||
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
||||
var password = "Admin@1234";
|
||||
await RegisterTenantAsync(tenantSlug, email, password);
|
||||
|
||||
// Act - Login
|
||||
var loginRequest = new { tenantSlug, email, password };
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<LoginResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.AccessToken.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithWrongPassword_ShouldFail()
|
||||
{
|
||||
// Arrange - Register tenant
|
||||
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
||||
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
||||
var password = "Admin@1234";
|
||||
await RegisterTenantAsync(tenantSlug, email, password);
|
||||
|
||||
// Act - Login with wrong password
|
||||
var loginRequest = new { tenantSlug, email, password = "WrongPassword123" };
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_WithNonExistentEmail_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var loginRequest = new
|
||||
{
|
||||
tenantSlug = "nonexistent",
|
||||
email = "nonexistent@test.com",
|
||||
password = "Password123"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AccessProtectedEndpoint_WithValidToken_ShouldSucceed()
|
||||
{
|
||||
// Arrange - Register and get token
|
||||
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
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var userInfo = await response.Content.ReadFromJsonAsync<UserInfoResponse>();
|
||||
userInfo.Should().NotBeNull();
|
||||
userInfo!.Email.Should().NotBeNullOrEmpty();
|
||||
userInfo.FullName.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AccessProtectedEndpoint_WithoutToken_ShouldFail()
|
||||
{
|
||||
// Arrange - No authorization header
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync("/api/auth/me");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AccessProtectedEndpoint_WithInvalidToken_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var invalidToken = "invalid.jwt.token";
|
||||
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", invalidToken);
|
||||
|
||||
// Act
|
||||
var response = await _client.GetAsync("/api/auth/me");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JwtToken_ShouldContainUserClaims()
|
||||
{
|
||||
// Arrange & Act
|
||||
var (accessToken, _) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Assert - Parse token and verify claims
|
||||
var claims = TestAuthHelper.ParseJwtToken(accessToken).ToList();
|
||||
|
||||
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");
|
||||
claims.Should().Contain(c => c.Type == "tenant_slug");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PasswordHashing_ShouldNotStorePlainTextPasswords()
|
||||
{
|
||||
// This is a conceptual test - in real implementation, you'd query the database
|
||||
// to verify passwords are hashed. Here we just verify that login works with BCrypt.
|
||||
|
||||
// Arrange - Register tenant
|
||||
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
||||
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
||||
var password = "Admin@1234";
|
||||
await RegisterTenantAsync(tenantSlug, email, password);
|
||||
|
||||
// Act - Login with correct password should work
|
||||
var correctLogin = await _client.PostAsJsonAsync("/api/auth/login",
|
||||
new { tenantSlug, email, password });
|
||||
|
||||
// Act - Login with wrong password should fail
|
||||
var wrongLogin = await _client.PostAsJsonAsync("/api/auth/login",
|
||||
new { tenantSlug, email, password = "WrongPassword" });
|
||||
|
||||
// Assert
|
||||
correctLogin.StatusCode.Should().Be(HttpStatusCode.OK,
|
||||
"Correct password should be verified against hashed password");
|
||||
wrongLogin.StatusCode.Should().Be(HttpStatusCode.Unauthorized,
|
||||
"Wrong password should not match hashed password");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteAuthFlow_RegisterLoginAccess_ShouldWork()
|
||||
{
|
||||
// This test verifies the complete authentication flow
|
||||
|
||||
// Step 1: Register
|
||||
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
||||
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
||||
var password = "Admin@1234";
|
||||
var registerResponse = await RegisterTenantAsync(tenantSlug, email, password);
|
||||
registerResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
// Step 2: Login
|
||||
var (loginToken, _) = await TestAuthHelper.LoginAndGetTokensAsync(_client, tenantSlug, email, password);
|
||||
loginToken.Should().NotBeNullOrEmpty();
|
||||
|
||||
// Step 3: Access Protected Endpoint
|
||||
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginToken);
|
||||
var meResponse = await _client.GetAsync("/api/auth/me");
|
||||
meResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var userInfo = await meResponse.Content.ReadFromJsonAsync<UserInfoResponse>();
|
||||
userInfo!.Email.Should().Be(email);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private async Task<HttpResponseMessage> RegisterTenantAsync(string tenantSlug, string email, string password)
|
||||
{
|
||||
var request = new
|
||||
{
|
||||
tenantName = "Test Corp",
|
||||
tenantSlug,
|
||||
subscriptionPlan = "Professional",
|
||||
adminEmail = email,
|
||||
adminPassword = password,
|
||||
adminFullName = "Test Admin"
|
||||
};
|
||||
|
||||
return await _client.PostAsJsonAsync("/api/tenants/register", request);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
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 : IClassFixture<DatabaseFixture>
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public RbacTests(DatabaseFixture fixture)
|
||||
{
|
||||
_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
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace ColaFlow.Modules.Identity.IntegrationTests.Identity;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for Refresh Token functionality (Day 5 - Phase 1)
|
||||
/// Tests token refresh flow, token rotation, and refresh token revocation
|
||||
/// </summary>
|
||||
public class RefreshTokenTests : IClassFixture<DatabaseFixture>
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public RefreshTokenTests(DatabaseFixture fixture)
|
||||
{
|
||||
_client = fixture.Client;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterTenant_ShouldReturnAccessAndRefreshTokens()
|
||||
{
|
||||
// Arrange
|
||||
var request = new
|
||||
{
|
||||
tenantName = "Test Corp",
|
||||
tenantSlug = $"test-{Guid.NewGuid():N}",
|
||||
subscriptionPlan = "Professional",
|
||||
adminEmail = $"admin-{Guid.NewGuid():N}@test.com",
|
||||
adminPassword = "Admin@1234",
|
||||
adminFullName = "Test Admin"
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/tenants/register", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<RegisterResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.AccessToken.Should().NotBeNullOrEmpty();
|
||||
result.RefreshToken.Should().NotBeNullOrEmpty();
|
||||
|
||||
// Verify tokens are different
|
||||
result.AccessToken.Should().NotBe(result.RefreshToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_ShouldReturnAccessAndRefreshTokens()
|
||||
{
|
||||
// Arrange - Register tenant first
|
||||
var tenantSlug = $"test-{Guid.NewGuid():N}";
|
||||
var email = $"admin-{Guid.NewGuid():N}@test.com";
|
||||
var password = "Admin@1234";
|
||||
|
||||
await RegisterTenantAsync(tenantSlug, email, password);
|
||||
|
||||
// Act - Login
|
||||
var loginRequest = new { tenantSlug, email, password };
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<LoginResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.AccessToken.Should().NotBeNullOrEmpty();
|
||||
result.RefreshToken.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_ShouldReturnNewTokenPair()
|
||||
{
|
||||
// Arrange - Register and get initial tokens
|
||||
var (accessToken, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Wait a moment to ensure token expiry time changes
|
||||
await Task.Delay(1000);
|
||||
|
||||
// Act - Refresh token
|
||||
var refreshRequest = new { refreshToken };
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/refresh", refreshRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
|
||||
result.Should().NotBeNull();
|
||||
result!.AccessToken.Should().NotBeNullOrEmpty();
|
||||
result.RefreshToken.Should().NotBeNullOrEmpty();
|
||||
|
||||
// New tokens should be different from old tokens
|
||||
result.AccessToken.Should().NotBe(accessToken);
|
||||
result.RefreshToken.Should().NotBe(refreshToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_WithOldToken_ShouldFail()
|
||||
{
|
||||
// Arrange - Register and get initial tokens
|
||||
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Act - Refresh once (invalidates old token)
|
||||
var firstRefresh = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
||||
firstRefresh.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
// Act - Try to reuse old refresh token
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
||||
|
||||
// Assert - Should fail because token is already used
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_WithInvalidToken_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var invalidToken = "invalid-refresh-token";
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken = invalidToken });
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Logout_ShouldRevokeRefreshToken()
|
||||
{
|
||||
// Arrange - Register and get tokens
|
||||
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Act - Logout
|
||||
var logoutResponse = await _client.PostAsJsonAsync("/api/auth/logout", new { refreshToken });
|
||||
|
||||
// Assert - Logout should succeed
|
||||
logoutResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
// Try to use revoked refresh token
|
||||
var refreshResponse = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
||||
|
||||
// Should fail because token is revoked
|
||||
refreshResponse.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_ShouldMaintainUserIdentity()
|
||||
{
|
||||
// Arrange - Register and get tokens
|
||||
var (accessToken, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Get original user info
|
||||
var originalUserId = TestAuthHelper.GetClaimValue(accessToken, "user_id");
|
||||
var originalTenantId = TestAuthHelper.GetClaimValue(accessToken, "tenant_id");
|
||||
|
||||
// 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 have same user identity
|
||||
var newUserId = TestAuthHelper.GetClaimValue(result!.AccessToken, "user_id");
|
||||
var newTenantId = TestAuthHelper.GetClaimValue(result.AccessToken, "tenant_id");
|
||||
|
||||
newUserId.Should().Be(originalUserId);
|
||||
newTenantId.Should().Be(originalTenantId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_Multiple_ShouldSucceed()
|
||||
{
|
||||
// Arrange - Register and get initial tokens
|
||||
var (_, refreshToken) = await TestAuthHelper.RegisterAndGetTokensAsync(_client);
|
||||
|
||||
// Act & Assert - Refresh multiple times
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken });
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<RefreshResponse>();
|
||||
result.Should().NotBeNull();
|
||||
|
||||
// Update refresh token for next iteration
|
||||
refreshToken = result!.RefreshToken;
|
||||
|
||||
await Task.Delay(500); // Small delay between requests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_Expired_ShouldFail()
|
||||
{
|
||||
// Note: This test requires the refresh token to be configured with a very short expiration time
|
||||
// In real scenarios, refresh tokens typically last 7-30 days
|
||||
// This test is a placeholder to document the expected behavior
|
||||
|
||||
// For now, we test with an invalid/non-existent token which should fail
|
||||
var expiredToken = Guid.NewGuid().ToString();
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("/api/auth/refresh", new { refreshToken = expiredToken });
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
Reference in New Issue
Block a user