using System.IdentityModel.Tokens.Jwt; using System.Net; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Text.Json; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.IdentityModel.Tokens; namespace WorkClub.Tests.Integration.Auth; public class AuthorizationTests : IClassFixture> { private readonly WebApplicationFactory _factory; public AuthorizationTests(WebApplicationFactory factory) { _factory = factory; } [Fact] public async Task AdminCanAccessAdminEndpoints_Returns200() { // Arrange var client = _factory.CreateClient(); var token = CreateTestJwtToken("admin@test.com", "club-1", "admin"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); client.DefaultRequestHeaders.Add("X-Tenant-Id", "club-1"); // Act - using health endpoint as placeholder for admin endpoint var response = await client.GetAsync("/health/ready"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] public async Task MemberCannotAccessAdminEndpoints_Returns403() { // Arrange var client = _factory.CreateClient(); var token = CreateTestJwtToken("member@test.com", "club-1", "member"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); client.DefaultRequestHeaders.Add("X-Tenant-Id", "club-1"); // Act - This will need actual admin endpoint in future (placeholder for now) var response = await client.GetAsync("/admin/test"); // Assert Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } [Fact] public async Task ViewerCanOnlyRead_PostReturns403() { // Arrange var client = _factory.CreateClient(); var token = CreateTestJwtToken("viewer@test.com", "club-1", "viewer"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); client.DefaultRequestHeaders.Add("X-Tenant-Id", "club-1"); // Act - Placeholder for actual POST endpoint var content = new StringContent("{}", Encoding.UTF8, "application/json"); var response = await client.PostAsync("/api/tasks", content); // Assert Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } [Fact] public async Task UnauthenticatedUser_Returns401() { // Arrange var client = _factory.CreateClient(); // No Authorization header // Act var response = await client.GetAsync("/api/tasks"); // Assert Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } [Fact] public async Task HealthEndpointsArePublic_NoAuthRequired() { // Arrange var client = _factory.CreateClient(); // No Authorization header // Act var liveResponse = await client.GetAsync("/health/live"); var readyResponse = await client.GetAsync("/health/ready"); var startupResponse = await client.GetAsync("/health/startup"); // Assert Assert.Equal(HttpStatusCode.OK, liveResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, readyResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, startupResponse.StatusCode); } /// /// Creates a test JWT token with specified user, club, and role /// private string CreateTestJwtToken(string username, string clubId, string role) { var clubsDict = new Dictionary { [clubId] = role }; var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, username), new Claim(JwtRegisteredClaimNames.Email, username), new Claim("clubs", JsonSerializer.Serialize(clubsDict)), new Claim(JwtRegisteredClaimNames.Aud, "workclub-api"), new Claim(JwtRegisteredClaimNames.Iss, "http://localhost:8080/realms/workclub") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("test-secret-key-must-be-at-least-32-chars-long-for-hmac-sha256")); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: "http://localhost:8080/realms/workclub", audience: "workclub-api", claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } }