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; /// /// Live-service tests against the dev GLAuth instance at localhost:3893. Skipped when /// the port is unreachable so the test suite stays portable. Verifies the bind path — /// group/role resolution is covered deterministically by , /// , and varies per directory (GLAuth, OpenLDAP, AD emit /// memberOf differently; the service has a DN-based fallback for the GLAuth case). /// [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.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); } }