225 lines
8.0 KiB
C#
225 lines
8.0 KiB
C#
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(DatabaseFixture fixture) : IClassFixture<DatabaseFixture>
|
|
{
|
|
private readonly HttpClient _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
|
|
}
|