chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Admin.Security;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Live-service tests against the dev GLAuth instance at <c>localhost:3893</c>. Skipped when
|
||||
/// the port is unreachable so the test suite stays portable. Verifies the bind path —
|
||||
/// group/role resolution is covered deterministically by <see cref="RoleMapperTests"/>,
|
||||
/// <see cref="LdapAuthServiceTests"/>, and varies per directory (GLAuth, OpenLDAP, AD emit
|
||||
/// <c>memberOf</c> differently; the service has a DN-based fallback for the GLAuth case).
|
||||
/// </summary>
|
||||
[Trait("Category", "LiveLdap")]
|
||||
public sealed class LdapLiveBindTests
|
||||
{
|
||||
private static bool GlauthReachable()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
var task = client.ConnectAsync("localhost", 3893);
|
||||
return task.Wait(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
private static LdapAuthService NewService() => new(Options.Create(new LdapOptions
|
||||
{
|
||||
Server = "localhost",
|
||||
Port = 3893,
|
||||
UseTls = false,
|
||||
AllowInsecureLdap = true,
|
||||
SearchBase = "dc=lmxopcua,dc=local",
|
||||
ServiceAccountDn = "", // direct-bind: GLAuth's nameformat=cn + baseDN means user DN is cn={name},{baseDN}
|
||||
GroupToRole = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["ReadOnly"] = "ConfigViewer",
|
||||
["WriteOperate"] = "ConfigEditor",
|
||||
["AlarmAck"] = "FleetAdmin",
|
||||
},
|
||||
}), NullLogger<LdapAuthService>.Instance);
|
||||
|
||||
[Fact]
|
||||
public async Task Valid_credentials_bind_successfully()
|
||||
{
|
||||
if (!GlauthReachable()) return;
|
||||
|
||||
var result = await NewService().AuthenticateAsync("readonly", "readonly123");
|
||||
|
||||
result.Success.ShouldBeTrue(result.Error);
|
||||
result.Username.ShouldBe("readonly");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Wrong_password_fails_bind()
|
||||
{
|
||||
if (!GlauthReachable()) return;
|
||||
|
||||
var result = await NewService().AuthenticateAsync("readonly", "wrong-pw");
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Error.ShouldContain("Invalid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Empty_username_is_rejected_before_hitting_the_directory()
|
||||
{
|
||||
// Doesn't need GLAuth — pre-flight validation in the service.
|
||||
var result = await NewService().AuthenticateAsync("", "anything");
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Error.ShouldContain("required", Case.Insensitive);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user