262 lines
9.2 KiB
C#
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
|
|
}
|