Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using JdeScoping.Infrastructure.Auth;
|
||||
using Shouldly;
|
||||
|
||||
namespace JdeScoping.Infrastructure.Tests.Unit;
|
||||
|
||||
public class FakeAuthServiceTests
|
||||
{
|
||||
private readonly FakeAuthService _sut;
|
||||
|
||||
public FakeAuthServiceTests()
|
||||
{
|
||||
_sut = new FakeAuthService();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticateAsync_WithValidCredentials_ReturnsSuccess()
|
||||
{
|
||||
// Act
|
||||
var result = await _sut.AuthenticateAsync("testuser", "password");
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue();
|
||||
result.User.ShouldNotBeNull();
|
||||
result.User.Username.ShouldBe("testuser");
|
||||
result.User.EmailAddress.ShouldBe("testuser@example.com");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticateAsync_AnyCredentials_ReturnsSuccess()
|
||||
{
|
||||
// FakeAuthService accepts any non-empty credentials
|
||||
var result = await _sut.AuthenticateAsync("anyuser", "anypassword");
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue();
|
||||
result.User.ShouldNotBeNull();
|
||||
result.ErrorMessage.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUserInfoAsync_ReturnsUserInfo()
|
||||
{
|
||||
// Act
|
||||
var result = await _sut.GetUserInfoAsync("testuser");
|
||||
|
||||
// Assert
|
||||
result.ShouldNotBeNull();
|
||||
result.Username.ShouldBe("testuser");
|
||||
result.FirstName.ShouldBe("Dev");
|
||||
result.LastName.ShouldBe("User");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsInGroupAsync_AlwaysReturnsTrue()
|
||||
{
|
||||
// Act
|
||||
var result = await _sut.IsInGroupAsync("testuser", "AnyGroup");
|
||||
|
||||
// Assert
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using JdeScoping.Core.Options;
|
||||
using JdeScoping.Infrastructure.Auth;
|
||||
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 IOptions<AuthOptions> _authOptions;
|
||||
private readonly ILogger<LdapAuthService> _logger;
|
||||
|
||||
public LdapAuthServiceTests()
|
||||
{
|
||||
_ldapOptions = Options.Create(new LdapOptions
|
||||
{
|
||||
ServerUrls = ["ldap.test.com"],
|
||||
GroupDn = "CN=TestGroup,DC=test,DC=com",
|
||||
SearchBase = "DC=test,DC=com",
|
||||
ConnectionTimeoutSeconds = 5
|
||||
});
|
||||
_authOptions = Options.Create(new AuthOptions
|
||||
{
|
||||
AdminBypassUsers = []
|
||||
});
|
||||
_logger = Substitute.For<ILogger<LdapAuthService>>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticateAsync_EmptyUsername_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var service = new LdapAuthService(_ldapOptions, _authOptions, _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, _authOptions, _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 = Options.Create(new LdapOptions
|
||||
{
|
||||
ServerUrls = [],
|
||||
GroupDn = "CN=TestGroup,DC=test,DC=com",
|
||||
SearchBase = "DC=test,DC=com"
|
||||
});
|
||||
var service = new LdapAuthService(emptyServerOptions, _authOptions, _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, _authOptions, _logger);
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<NotSupportedException>(() => 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 authOptionsWithBypass = Options.Create(new AuthOptions
|
||||
{
|
||||
AdminBypassUsers = ["bypassuser", "adminuser"]
|
||||
});
|
||||
var service = new LdapAuthService(_ldapOptions, authOptionsWithBypass, _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 = 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
|
||||
});
|
||||
var service = new LdapAuthService(multiServerOptions, _authOptions, _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
|
||||
}
|
||||
Reference in New Issue
Block a user