185 lines
7.2 KiB
C#
185 lines
7.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Google.Protobuf.Collections;
|
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
|
using ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;
|
|
|
|
public sealed class AlarmAckWriterTests
|
|
{
|
|
private static AlarmAckOperator Alice() =>
|
|
new AlarmAckOperator { UserName = "alice", Domain = "CORP", Password = "pw-secret" };
|
|
|
|
private static RepeatedField<MxStatusProxy> Rows(MxStatusCategory category, int detail) =>
|
|
new RepeatedField<MxStatusProxy> { new MxStatusProxy { Success = category == MxStatusCategory.Ok ? 1 : 0, Category = category, Detail = detail } };
|
|
|
|
[Fact]
|
|
public void Acknowledge_AuthenticatesThenWritesCommentWithOperatorUserId()
|
|
{
|
|
RecordingAckServer server = new RecordingAckServer { AuthenticateResult = 5 };
|
|
MxAccessWriteCompletionCache cache = new MxAccessWriteCompletionCache();
|
|
server.OnWrite = (handle, item) => cache.Record(handle, item, Rows(MxStatusCategory.Ok, 0));
|
|
using AlarmAckWriter writer = new AlarmAckWriter(server, cache, () => { }, TimeSpan.FromSeconds(1), "MxGateway");
|
|
|
|
AlarmAckResult result = writer.Acknowledge("T.A.AckMsg", "checked", Alice());
|
|
|
|
Assert.Equal(AlarmAckOutcome.Applied, result.Outcome);
|
|
Assert.Equal(0, result.NativeStatus);
|
|
Assert.Equal(new[] { "Register:MxGateway", "AuthenticateUser:CORP\\alice", "AddItem:T.A.AckMsg", "Advise", "Write:checked:5" }, server.Operations);
|
|
Assert.Equal("pw-secret", server.LastPassword);
|
|
}
|
|
|
|
[Fact]
|
|
public void Acknowledge_WhenWriteRejected_ReturnsDetailAndStatuses()
|
|
{
|
|
RecordingAckServer server = new RecordingAckServer { AuthenticateResult = 5 };
|
|
MxAccessWriteCompletionCache cache = new MxAccessWriteCompletionCache();
|
|
server.OnWrite = (handle, item) => cache.Record(handle, item, Rows(MxStatusCategory.SecurityError, 1008));
|
|
using AlarmAckWriter writer = new AlarmAckWriter(server, cache, () => { }, TimeSpan.FromSeconds(1), "MxGateway");
|
|
|
|
AlarmAckResult result = writer.Acknowledge("T.A.AckMsg", "checked", Alice());
|
|
|
|
Assert.Equal(AlarmAckOutcome.WriteRejected, result.Outcome);
|
|
Assert.Equal(1008, result.NativeStatus);
|
|
Assert.Single(result.Statuses);
|
|
}
|
|
|
|
[Fact]
|
|
public void Acknowledge_WhenNoCompletion_ReturnsUnconfirmed()
|
|
{
|
|
RecordingAckServer server = new RecordingAckServer { AuthenticateResult = 5 };
|
|
using AlarmAckWriter writer = new AlarmAckWriter(server, new MxAccessWriteCompletionCache(), () => { }, TimeSpan.FromMilliseconds(50), "MxGateway");
|
|
|
|
AlarmAckResult result = writer.Acknowledge("T.A.AckMsg", "checked", Alice());
|
|
|
|
Assert.Equal(AlarmAckOutcome.Unconfirmed, result.Outcome);
|
|
Assert.Equal(0, result.NativeStatus);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void Acknowledge_WhenAuthenticationFails_DoesNotWriteOrLeakPassword(bool throwComException)
|
|
{
|
|
RecordingAckServer server = new RecordingAckServer
|
|
{
|
|
AuthenticateResult = 0,
|
|
AuthenticateException = throwComException ? new COMException("bad credentials", unchecked((int)0x80070005)) : null,
|
|
};
|
|
using AlarmAckWriter writer = new AlarmAckWriter(server, new MxAccessWriteCompletionCache(), () => { }, TimeSpan.FromMilliseconds(50), "MxGateway");
|
|
|
|
AlarmAckResult result = writer.Acknowledge("T.A.AckMsg", "checked", Alice());
|
|
|
|
Assert.Equal(AlarmAckOutcome.AuthenticationFailed, result.Outcome);
|
|
Assert.NotEqual(0, result.NativeStatus);
|
|
Assert.DoesNotContain(server.Operations, op => op.StartsWith("Write", StringComparison.Ordinal));
|
|
Assert.DoesNotContain("pw-secret", result.Diagnostic, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(server.Operations, op => op.Contains("pw-secret"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Acknowledge_Twice_ReusesItemHandle()
|
|
{
|
|
RecordingAckServer server = new RecordingAckServer { AuthenticateResult = 5 };
|
|
using AlarmAckWriter writer = new AlarmAckWriter(server, new MxAccessWriteCompletionCache(), () => { }, TimeSpan.FromMilliseconds(10), "MxGateway");
|
|
|
|
writer.Acknowledge("T.A.AckMsg", "one", Alice());
|
|
writer.Acknowledge("T.A.AckMsg", "two", Alice());
|
|
|
|
Assert.Equal(1, server.Operations.Count(op => op.StartsWith("AddItem", StringComparison.Ordinal)));
|
|
Assert.Equal(1, server.Operations.Count(op => op == "Advise"));
|
|
}
|
|
|
|
[Fact]
|
|
public void AlarmAckOperator_ToString_OmitsPassword()
|
|
{
|
|
Assert.DoesNotContain("pw-secret", Alice().ToString(), StringComparison.Ordinal);
|
|
}
|
|
}
|
|
|
|
internal sealed class RecordingAckServer : IMxAccessServer
|
|
{
|
|
private int nextItemHandle = 100;
|
|
|
|
public List<string> Operations { get; } = new();
|
|
|
|
public int AuthenticateResult { get; set; }
|
|
|
|
public Exception? AuthenticateException { get; set; }
|
|
|
|
public string? LastPassword { get; private set; }
|
|
|
|
public Action<int, int>? OnWrite { get; set; }
|
|
|
|
public int Register(string clientName)
|
|
{
|
|
Operations.Add("Register:" + clientName);
|
|
return 7;
|
|
}
|
|
|
|
public void Unregister(int serverHandle) => Operations.Add("Unregister");
|
|
|
|
public int AddItem(int serverHandle, string itemDefinition)
|
|
{
|
|
Operations.Add("AddItem:" + itemDefinition);
|
|
return nextItemHandle++;
|
|
}
|
|
|
|
public int AddItem2(int serverHandle, string itemDefinition, string itemContext) => AddItem(serverHandle, itemDefinition);
|
|
|
|
public void RemoveItem(int serverHandle, int itemHandle) => Operations.Add("RemoveItem");
|
|
|
|
public void Advise(int serverHandle, int itemHandle) => Operations.Add("Advise");
|
|
|
|
public void UnAdvise(int serverHandle, int itemHandle) => Operations.Add("UnAdvise");
|
|
|
|
public void AdviseSupervisory(int serverHandle, int itemHandle)
|
|
{
|
|
}
|
|
|
|
public void Write(int serverHandle, int itemHandle, object? value, int userId)
|
|
{
|
|
Operations.Add("Write:" + value + ":" + userId);
|
|
OnWrite?.Invoke(serverHandle, itemHandle);
|
|
}
|
|
|
|
public void Write2(int serverHandle, int itemHandle, object? value, object? timestamp, int userId)
|
|
{
|
|
}
|
|
|
|
public void WriteSecured(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value)
|
|
{
|
|
}
|
|
|
|
public void WriteSecured2(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value, object? timestamp)
|
|
{
|
|
}
|
|
|
|
public object Suspend(int serverHandle, int itemHandle) => new object();
|
|
|
|
public object Activate(int serverHandle, int itemHandle) => new object();
|
|
|
|
public int AuthenticateUser(int serverHandle, string verifyUser, string verifyUserPassword)
|
|
{
|
|
Operations.Add("AuthenticateUser:" + verifyUser);
|
|
LastPassword = verifyUserPassword;
|
|
if (AuthenticateException is not null)
|
|
{
|
|
throw AuthenticateException;
|
|
}
|
|
|
|
return AuthenticateResult;
|
|
}
|
|
|
|
public int ArchestrAUserToId(int serverHandle, string userIdGuid) => 0;
|
|
|
|
public int AddBufferedItem(int serverHandle, string itemDefinition, string itemContext) => AddItem(serverHandle, itemDefinition);
|
|
|
|
public void SetBufferedUpdateInterval(int serverHandle, int updateIntervalMilliseconds)
|
|
{
|
|
}
|
|
}
|