Files
Joseph Doherty 604bfe919c refactor: address code review findings across all projects
Apply comprehensive fixes from code reviews including:
- Extract shared utilities (SqlFormatHelper, CellValueConverter, DbDestinationBase)
- Add interface abstractions (IAuthenticationService, IDatabaseMigrator, IMisQueryBuilder)
- Implement SecureStore for encrypted secrets storage
- Fix error handling with proper HTTP status codes and logging
- Optimize double enumeration in DevEtlRegistry
- Add DataSync.Dev README for developer onboarding
- Extract filter panel base classes to reduce duplication
- Update code review docs to mark all issues as fixed
2026-01-19 11:05:36 -05:00

132 lines
5.1 KiB
C#

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> _ldapOptions;
private readonly ILogger<LdapAuthService> _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<ILogger<LdapAuthService>>();
}
[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 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
}