Resolve Tests-001 and Tests-002 code-review findings
Tests-001: FakeSessionManager.TryGetSession unconditionally synthesized a session, so Invoke_WhenSessionMissing_ThrowsNotFound did not actually verify the missing-session path. Added ResolveOnlySeededSessions/SeedSession to the fake, rewrote the missing-session test, and added seeded-resolution and alarm-RPC missing-session coverage. Tests-002: re-triaged. GalaxyRepository issues only constant SQL; filters are applied in-memory by GalaxyHierarchyProjector/GalaxyGlobMatcher. Kept as a valid coverage gap and added GalaxyFilterInputSafetyTests exercising filter/glob input safety directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,16 +47,17 @@ public sealed class MxAccessGatewayServiceTests
|
||||
Assert.Equal("operator-session", sessionManager.LastOpenRequest?.ClientSessionName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Invoke throws NotFound when the session does not exist.</summary>
|
||||
/// <summary>
|
||||
/// Verifies that Invoke maps a genuinely missing session to NotFound via the
|
||||
/// service's own <c>ResolveSession</c> lookup. No <c>InvokeException</c> is
|
||||
/// injected — <see cref="FakeSessionManager.ResolveOnlySeededSessions"/> makes
|
||||
/// <c>TryGetSession</c> return false, so this test fails if the service drops
|
||||
/// its missing-session check rather than passing for the wrong reason.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Invoke_WhenSessionMissing_ThrowsNotFound()
|
||||
{
|
||||
FakeSessionManager sessionManager = new()
|
||||
{
|
||||
InvokeException = new SessionManagerException(
|
||||
SessionManagerErrorCode.SessionNotFound,
|
||||
"Session session-missing was not found."),
|
||||
};
|
||||
FakeSessionManager sessionManager = new() { ResolveOnlySeededSessions = true };
|
||||
MxAccessGatewayService service = CreateService(sessionManager);
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
@@ -66,6 +67,76 @@ public sealed class MxAccessGatewayServiceTests
|
||||
|
||||
Assert.Equal(StatusCode.NotFound, exception.StatusCode);
|
||||
Assert.Contains("session-missing", exception.Status.Detail, StringComparison.Ordinal);
|
||||
// The service must reject before delegating to the session manager.
|
||||
Assert.Equal(0, sessionManager.InvokeCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Invoke resolves a session that was seeded into the session
|
||||
/// manager when <see cref="FakeSessionManager.ResolveOnlySeededSessions"/> is on,
|
||||
/// confirming the missing-session test above is gated on a real lookup.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Invoke_WhenSessionSeeded_ResolvesAndInvokes()
|
||||
{
|
||||
FakeSessionManager sessionManager = new() { ResolveOnlySeededSessions = true };
|
||||
sessionManager.SeedSession(CreateSession("session-1", processId: 1234));
|
||||
MxAccessGatewayService service = CreateService(sessionManager);
|
||||
|
||||
MxCommandReply reply = await service.Invoke(
|
||||
CreatePingRequest("session-1"),
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||
Assert.Equal(1, sessionManager.InvokeCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AcknowledgeAlarm maps a genuinely missing session to NotFound via
|
||||
/// the service's own <c>ResolveSession</c> lookup rather than an injected exception.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAlarm_WhenSessionMissing_ThrowsNotFound()
|
||||
{
|
||||
FakeSessionManager sessionManager = new() { ResolveOnlySeededSessions = true };
|
||||
MxAccessGatewayService service = CreateService(sessionManager);
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.AcknowledgeAlarm(
|
||||
new AcknowledgeAlarmRequest
|
||||
{
|
||||
SessionId = "session-missing",
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
OperatorUser = "alice",
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.NotFound, exception.StatusCode);
|
||||
Assert.Contains("session-missing", exception.Status.Detail, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that QueryActiveAlarms maps a genuinely missing session to NotFound via
|
||||
/// the service's own <c>ResolveSession</c> lookup rather than an injected exception.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task QueryActiveAlarms_WhenSessionMissing_ThrowsNotFound()
|
||||
{
|
||||
FakeSessionManager sessionManager = new() { ResolveOnlySeededSessions = true };
|
||||
MxAccessGatewayService service = CreateService(sessionManager);
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.QueryActiveAlarms(
|
||||
new QueryActiveAlarmsRequest
|
||||
{
|
||||
SessionId = "session-missing",
|
||||
AlarmFilterPrefix = "Tank01.",
|
||||
},
|
||||
new RecordingStreamWriter<ActiveAlarmSnapshot>(),
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.NotFound, exception.StatusCode);
|
||||
Assert.Contains("session-missing", exception.Status.Detail, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Invoke throws InvalidArgument and does not invoke the session manager when payload is mismatched.</summary>
|
||||
@@ -425,9 +496,26 @@ public sealed class MxAccessGatewayServiceTests
|
||||
|
||||
private sealed class FakeSessionManager : ISessionManager
|
||||
{
|
||||
private readonly Dictionary<string, GatewaySession> seededSessions = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>The session to return from OpenSessionAsync.</summary>
|
||||
public GatewaySession? OpenSessionResult { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, <see cref="TryGetSession"/> only resolves sessions that have been
|
||||
/// explicitly seeded via <see cref="SeedSession"/> (or <see cref="OpenSessionResult"/>),
|
||||
/// and returns false for any other id. This exercises the gateway service's own
|
||||
/// missing-session handling instead of masking it with a synthesized session.
|
||||
/// </summary>
|
||||
public bool ResolveOnlySeededSessions { get; init; }
|
||||
|
||||
/// <summary>Registers a session so <see cref="TryGetSession"/> resolves its id.</summary>
|
||||
/// <param name="session">Session to register by its <see cref="GatewaySession.SessionId"/>.</param>
|
||||
public void SeedSession(GatewaySession session)
|
||||
{
|
||||
seededSessions[session.SessionId] = session;
|
||||
}
|
||||
|
||||
/// <summary>The last OpenSessionAsync request captured.</summary>
|
||||
public SessionOpenRequest? LastOpenRequest { get; private set; }
|
||||
|
||||
@@ -484,6 +572,18 @@ public sealed class MxAccessGatewayServiceTests
|
||||
string sessionId,
|
||||
out GatewaySession session)
|
||||
{
|
||||
if (seededSessions.TryGetValue(sessionId, out GatewaySession? seeded))
|
||||
{
|
||||
session = seeded;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ResolveOnlySeededSessions)
|
||||
{
|
||||
session = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
session = OpenSessionResult ?? CreateSession(sessionId, processId: 1234);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user