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

262 lines
9.2 KiB
C#

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(DatabaseFixture fixture) : IClassFixture<DatabaseFixture>
{
private readonly HttpClient _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
}