Files
ColaFlow/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Identity/RefreshTokenTests.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

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
}