using JdeScoping.Infrastructure.Auth; using JdeScoping.Infrastructure.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSubstitute; using Shouldly; namespace JdeScoping.Infrastructure.Tests.Unit; public class LdapAuthServiceTests { private readonly IOptions _ldapOptions; private readonly ILogger _logger; public LdapAuthServiceTests() { _ldapOptions = Microsoft.Extensions.Options.Options.Create(new LdapOptions { ServerUrls = ["ldap.test.com"], GroupDn = "CN=TestGroup,DC=test,DC=com", SearchBase = "DC=test,DC=com", ConnectionTimeoutSeconds = 5, AdminBypassUsers = [] }); _logger = Substitute.For>(); } [Fact] public async Task AuthenticateAsync_EmptyUsername_ReturnsFailure() { // Arrange var service = new LdapAuthService(_ldapOptions, _logger); // Act var result = await service.AuthenticateAsync("", "password"); // Assert result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldBe("Username and password are required"); } [Fact] public async Task AuthenticateAsync_EmptyPassword_ReturnsFailure() { // Arrange var service = new LdapAuthService(_ldapOptions, _logger); // Act var result = await service.AuthenticateAsync("user", ""); // Assert result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldBe("Username and password are required"); } [Fact] public async Task AuthenticateAsync_NoServersConfigured_ReturnsConnectionError() { // Arrange var emptyServerOptions = Microsoft.Extensions.Options.Options.Create(new LdapOptions { ServerUrls = [], GroupDn = "CN=TestGroup,DC=test,DC=com", SearchBase = "DC=test,DC=com", AdminBypassUsers = [] }); var service = new LdapAuthService(emptyServerOptions, _logger); // Act var result = await service.AuthenticateAsync("user", "password"); // Assert result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldBe("Unable to connect to directory server"); } [Fact] public void GetUserInfoAsync_ThrowsNotSupportedException() { // Arrange var service = new LdapAuthService(_ldapOptions, _logger); // Act & Assert Should.Throw(() => service.GetUserInfoAsync("user").GetAwaiter().GetResult()); } [Fact] public async Task AuthenticateAsync_AdminBypassUser_ConfigurationIsRecognized() { // Arrange // Note: We can't fully test admin bypass without a real LDAP server since bind still happens. // This test verifies the configuration is recognized by checking that bypass users are configured. var ldapOptionsWithBypass = Microsoft.Extensions.Options.Options.Create(new LdapOptions { ServerUrls = ["ldap.test.com"], GroupDn = "CN=TestGroup,DC=test,DC=com", SearchBase = "DC=test,DC=com", ConnectionTimeoutSeconds = 5, AdminBypassUsers = ["bypassuser", "adminuser"] }); var service = new LdapAuthService(ldapOptionsWithBypass, _logger); // Act - attempt to authenticate the bypass user (will fail LDAP connection, but config is exercised) var result = await service.AuthenticateAsync("bypassuser", "anypassword"); // Assert - since we don't have a real LDAP server, connection will fail // but the admin bypass configuration code path is exercised result.Success.ShouldBeFalse(); // The error should be connection-related, not "Username and password are required" result.ErrorMessage.ShouldNotBe("Username and password are required"); } [Fact] public async Task AuthenticateAsync_MultipleServersConfigured_TriesEachUntilAllFail() { // Arrange var multiServerOptions = Microsoft.Extensions.Options.Options.Create(new LdapOptions { ServerUrls = ["ldap1.test.com", "ldap2.test.com", "ldap3.test.com"], GroupDn = "CN=TestGroup,DC=test,DC=com", SearchBase = "DC=test,DC=com", ConnectionTimeoutSeconds = 1, // Fast timeout for test AdminBypassUsers = [] }); var service = new LdapAuthService(multiServerOptions, _logger); // Act var result = await service.AuthenticateAsync("testuser", "testpassword"); // Assert - when all servers fail, authentication fails // Note: Error message varies by platform - "Unable to connect to directory server" on Windows, // "The feature is not supported." on macOS (where LDAP is not natively supported) result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldNotBeNullOrWhiteSpace(); // Verify it's not a validation error (which would indicate we didn't try the servers) result.ErrorMessage.ShouldNotBe("Username and password are required"); } // Note: Testing actual LDAP connections requires integration tests with a real/mock LDAP server // These unit tests cover the basic validation and edge cases }