Commit all scripts
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

This commit is contained in:
Yaojia Wang
2025-11-03 17:19:20 +01:00
parent ebdd4ee0d7
commit 4183b10b39
24 changed files with 4917 additions and 11 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}