using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using SlipItIn.Server.Controllers; using SlipItIn.Server.Data; using SlipItIn.Shared.DTOs; using SlipItIn.Shared.Models; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; namespace SlipItIn.Server.Tests; public class AuthControllerTests { private readonly IDbContextFactory _factory = TestDbHelper.CreateFactory(); private readonly AuthController _sut; public AuthControllerTests() { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Jwt:Key"] = "TestKeyThatIsAtLeast32CharactersLong!!", ["Jwt:Issuer"] = "TestIssuer", ["Jwt:Audience"] = "TestAudience", ["Jwt:ExpirationMinutes"] = "60" }) .Build(); _sut = new AuthController(_factory, config, new TestHostEnvironment()); } private sealed class TestHostEnvironment : IHostEnvironment { public string EnvironmentName { get; set; } = Environments.Development; public string ApplicationName { get; set; } = "SlipItIn.Server.Tests"; public string ContentRootPath { get; set; } = AppContext.BaseDirectory; public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!; } // ---------- Register ---------- [Fact] public async Task Register_ReturnsOk_WithTokenAndUserData() { var result = await _sut.Register(new RegisterRequestDto { Username = " Alice ", Email = " ALICE@Test.Local ", Password = "Secret123!" }); var ok = Assert.IsType(result.Result); var dto = Assert.IsType(ok.Value); Assert.Equal("Alice", dto.Username); // getrimmt Assert.Equal("alice@test.local", dto.Email); // getrimmt + lowercase Assert.True(dto.UserId > 0); Assert.False(string.IsNullOrWhiteSpace(dto.Token)); Assert.True(dto.ExpiresAtUtc > DateTime.UtcNow); // Token enthält die erwarteten Claims und ist valide lesbar var token = new JwtSecurityTokenHandler().ReadJwtToken(dto.Token); Assert.Equal("TestIssuer", token.Issuer); Assert.Contains("TestAudience", token.Audiences); Assert.Equal(dto.UserId.ToString(), token.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value); } [Theory] [InlineData("", "a@b.c", "pw")] [InlineData("user", "", "pw")] [InlineData("user", "a@b.c", "")] [InlineData(" ", "a@b.c", "pw")] public async Task Register_ReturnsBadRequest_WhenFieldsMissing(string username, string email, string password) { var result = await _sut.Register(new RegisterRequestDto { Username = username, Email = email, Password = password }); Assert.IsType(result.Result); } [Fact] public async Task Register_ReturnsConflict_WhenEmailExists_CaseInsensitive() { using var db = _factory.CreateDbContext(); TestDbHelper.SeedUser(db, "existing", "alice@test.local"); var result = await _sut.Register(new RegisterRequestDto { Username = "newuser", Email = "ALICE@TEST.LOCAL", Password = "Secret123!" }); Assert.IsType(result.Result); } [Fact] public async Task Register_ReturnsConflict_WhenUsernameExists_CaseInsensitive() { using var db = _factory.CreateDbContext(); TestDbHelper.SeedUser(db, "alice", "other@test.local"); var result = await _sut.Register(new RegisterRequestDto { Username = "ALICE", Email = "new@test.local", Password = "Secret123!" }); Assert.IsType(result.Result); } // ---------- Login ---------- [Fact] public async Task Login_ReturnsOk_WithValidCredentials() { using var db = _factory.CreateDbContext(); db.Users.Add(new User { Username = "bob", Email = "bob@test.local", PasswordHash = BCrypt.Net.BCrypt.HashPassword("Secret123!") }); await db.SaveChangesAsync(); var result = await _sut.Login(new LoginRequestDto { Email = " BOB@test.local ", Password = "Secret123!" }); var ok = Assert.IsType(result.Result); var dto = Assert.IsType(ok.Value); Assert.Equal("bob", dto.Username); Assert.False(string.IsNullOrWhiteSpace(dto.Token)); } [Theory] [InlineData("", "pw")] [InlineData("a@b.c", "")] public async Task Login_ReturnsBadRequest_WhenFieldsMissing(string email, string password) { var result = await _sut.Login(new LoginRequestDto { Email = email, Password = password }); Assert.IsType(result.Result); } [Fact] public async Task Login_ReturnsUnauthorized_WhenUserNotFound() { var result = await _sut.Login(new LoginRequestDto { Email = "ghost@test.local", Password = "Secret123!" }); Assert.IsType(result.Result); } [Fact] public async Task Login_ReturnsUnauthorized_WhenPasswordWrong() { using var db = _factory.CreateDbContext(); db.Users.Add(new User { Username = "bob", Email = "bob@test.local", PasswordHash = BCrypt.Net.BCrypt.HashPassword("CorrectPassword") }); await db.SaveChangesAsync(); var result = await _sut.Login(new LoginRequestDto { Email = "bob@test.local", Password = "WrongPassword" }); Assert.IsType(result.Result); } // ---------- Me ---------- [Fact] public void Me_ReturnsUserData_FromClaims() { var identity = new ClaimsIdentity([ new Claim(ClaimTypes.NameIdentifier, "42"), new Claim(ClaimTypes.Name, "alice"), new Claim(ClaimTypes.Email, "alice@test.local") ], "TestAuth"); _sut.ControllerContext = new ControllerContext { HttpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext { User = new ClaimsPrincipal(identity) } }; var result = _sut.Me(); var ok = Assert.IsType(result.Result); var value = ok.Value!; Assert.Equal(42, (int)value.GetType().GetProperty("UserId")!.GetValue(value)!); Assert.Equal("alice", (string)value.GetType().GetProperty("Username")!.GetValue(value)!); Assert.Equal("alice@test.local", (string)value.GetType().GetProperty("Email")!.GetValue(value)!); } [Fact] public void Me_ReturnsUnauthorized_WhenNameIdentifierClaimMissing() { var identity = new ClaimsIdentity([new Claim(ClaimTypes.Name, "alice")], "TestAuth"); _sut.ControllerContext = new ControllerContext { HttpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext { User = new ClaimsPrincipal(identity) } }; var result = _sut.Me(); Assert.IsType(result.Result); } }