refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Reflection-level contract guards for the Audit Log (#23) writer interfaces.
|
||||
/// Locks in method signature so DI bindings + adapter implementations stay aligned.
|
||||
/// </summary>
|
||||
public class AuditWriterContractTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(IAuditWriter))]
|
||||
[InlineData(typeof(ICentralAuditWriter))]
|
||||
public void WriteAsync_HasExpectedSignature(Type writerType)
|
||||
{
|
||||
var method = writerType.GetMethod("WriteAsync", BindingFlags.Instance | BindingFlags.Public);
|
||||
Assert.NotNull(method);
|
||||
Assert.Equal(typeof(Task), method!.ReturnType);
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
Assert.Equal(2, parameters.Length);
|
||||
Assert.Equal(typeof(AuditEvent), parameters[0].ParameterType);
|
||||
Assert.Equal(typeof(CancellationToken), parameters[1].ParameterType);
|
||||
Assert.True(parameters[1].HasDefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAuditWriter_AndICentralAuditWriter_AreDistinctTypes()
|
||||
{
|
||||
Assert.NotEqual(typeof(IAuditWriter), typeof(ICentralAuditWriter));
|
||||
Assert.False(typeof(IAuditWriter).IsAssignableFrom(typeof(ICentralAuditWriter)));
|
||||
Assert.False(typeof(ICentralAuditWriter).IsAssignableFrom(typeof(IAuditWriter)));
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ExternalCallResult"/>, in particular the Commons-021
|
||||
/// thread-safe lazy parse of <c>Response</c>. The pre-fix implementation used
|
||||
/// two mutable fields (<c>_response</c>/<c>_responseParsed</c>) with no
|
||||
/// synchronization, so concurrent readers could each construct a fresh
|
||||
/// <c>DynamicJsonElement</c> and one would overwrite the other. The fix moves
|
||||
/// the parse onto a <c>Lazy<dynamic?></c> with
|
||||
/// <c>LazyThreadSafetyMode.ExecutionAndPublication</c> (the default), which
|
||||
/// guarantees one parse and one shared result for all readers.
|
||||
/// </summary>
|
||||
public class ExternalCallResultTests
|
||||
{
|
||||
[Fact]
|
||||
public void Response_NullOrEmptyJson_ReturnsNull()
|
||||
{
|
||||
var withNull = new ExternalCallResult(Success: true, ResponseJson: null, ErrorMessage: null);
|
||||
var withEmpty = new ExternalCallResult(Success: true, ResponseJson: string.Empty, ErrorMessage: null);
|
||||
|
||||
Assert.Null(withNull.Response);
|
||||
Assert.Null(withEmpty.Response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Response_ParsesJsonIntoDynamicElement()
|
||||
{
|
||||
var result = new ExternalCallResult(Success: true, ResponseJson: "{\"answer\": 42}", ErrorMessage: null);
|
||||
|
||||
// dynamic property access is the production usage pattern.
|
||||
dynamic? response = result.Response;
|
||||
Assert.NotNull(response);
|
||||
int answer = (int)response!.answer;
|
||||
Assert.Equal(42, answer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commons-021: concurrent readers must observe the same parsed instance
|
||||
/// (a `Lazy<T>` invariant). Under the pre-fix code two threads could
|
||||
/// both produce a fresh `DynamicJsonElement` and one would win the race —
|
||||
/// `ReferenceEquals` would then occasionally fail. With the fix every
|
||||
/// reader observes the single Lazy-published value, so the assertion
|
||||
/// holds for every pair of observers.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Response_ConcurrentReads_ReturnSameInstance()
|
||||
{
|
||||
// A larger payload makes the parse window wider so the race, if
|
||||
// present, is more likely to fire. The same property — single
|
||||
// published instance — must hold for any payload, though.
|
||||
var json = "{\"items\":[{\"name\":\"a\"},{\"name\":\"b\"},{\"name\":\"c\"}],\"count\":3}";
|
||||
var result = new ExternalCallResult(Success: true, ResponseJson: json, ErrorMessage: null);
|
||||
|
||||
const int observerCount = 64;
|
||||
var barrier = new Barrier(observerCount);
|
||||
var observed = new object?[observerCount];
|
||||
|
||||
Parallel.For(0, observerCount, i =>
|
||||
{
|
||||
// Force all observers to call `Response` at the same instant so
|
||||
// they collide on the lazy parse rather than each finding it
|
||||
// already-published.
|
||||
barrier.SignalAndWait();
|
||||
observed[i] = result.Response;
|
||||
});
|
||||
|
||||
var first = observed[0];
|
||||
Assert.NotNull(first);
|
||||
for (var i = 1; i < observerCount; i++)
|
||||
{
|
||||
Assert.Same(first, observed[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user