test: cover WatchDeployEvents scoped-count re-projection
This commit is contained in:
+81
-7
@@ -63,18 +63,78 @@ public sealed class GalaxyRepositoryGrpcServiceScopeTests
|
||||
Assert.Equal(0, scoped.TotalChildCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the scope provider returns a non-empty glob, the deploy event's
|
||||
/// object/attribute counts are re-projected against the scoped subtree and override
|
||||
/// the raw counts the notifier published.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task WatchDeployEvents_ScopedProvider_EmitsFilteredCounts()
|
||||
{
|
||||
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
||||
|
||||
// Sanity: the full hierarchy projects to six objects / four attributes.
|
||||
GalaxyHierarchyQueryResult full = GalaxyHierarchyProjector.Project(
|
||||
entry,
|
||||
new DiscoverHierarchyRequest());
|
||||
Assert.Equal(6, full.TotalObjectCount);
|
||||
Assert.Equal(4, full.Objects.Sum(obj => obj.Attributes.Count));
|
||||
|
||||
// The glob selects only LineA's two leaf objects (Pump01, Valve01), each with one
|
||||
// attribute. That scoped projection (2 objects / 2 attributes) is a non-empty subset
|
||||
// distinct from both the full count and the raw notifier values below.
|
||||
GalaxyHierarchyQueryResult scopedProjection = GalaxyHierarchyProjector.Project(
|
||||
entry,
|
||||
new DiscoverHierarchyRequest(),
|
||||
browseSubtreeGlobs: ["PlantArea/LineA/*"]);
|
||||
Assert.Equal(2, scopedProjection.TotalObjectCount);
|
||||
Assert.Equal(2, scopedProjection.Objects.Sum(obj => obj.Attributes.Count));
|
||||
|
||||
// Publish a deploy event whose RAW counts differ from both full and scoped, so an
|
||||
// assertion on the scoped values proves the override actually happened.
|
||||
RecordingDeployNotifier notifier = new();
|
||||
notifier.Publish(new GalaxyDeployEventInfo(
|
||||
Sequence: 42,
|
||||
ObservedAt: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
TimeOfLastDeploy: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
ObjectCount: 999,
|
||||
AttributeCount: 888));
|
||||
|
||||
GalaxyRepositoryGrpcService service = CreateService(
|
||||
entry,
|
||||
new FakeBrowseScopeProvider(subtrees: ["PlantArea/LineA/*"]),
|
||||
notifier);
|
||||
|
||||
// RecordingDeployNotifier yields the latest event then completes, so the stream
|
||||
// ends after the single event without needing cancellation.
|
||||
CapturingStreamWriter responseStream = new();
|
||||
await service.WatchDeployEvents(
|
||||
new WatchDeployEventsRequest(),
|
||||
responseStream,
|
||||
new TestServerCallContext());
|
||||
|
||||
DeployEvent emitted = Assert.Single(responseStream.Written);
|
||||
Assert.Equal(scopedProjection.TotalObjectCount, emitted.ObjectCount);
|
||||
Assert.Equal(2, emitted.AttributeCount);
|
||||
// The raw notifier values were overridden by the scoped re-projection.
|
||||
Assert.NotEqual(999, emitted.ObjectCount);
|
||||
Assert.NotEqual(888, emitted.AttributeCount);
|
||||
}
|
||||
|
||||
private static GalaxyRepositoryGrpcService CreateService(
|
||||
GalaxyHierarchyCacheEntry entry,
|
||||
IGalaxyBrowseScopeProvider scope)
|
||||
IGalaxyBrowseScopeProvider scope,
|
||||
IGalaxyDeployNotifier? notifier = null)
|
||||
{
|
||||
GalaxyRepositoryOptions options = new()
|
||||
{
|
||||
ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;Encrypt=False;",
|
||||
};
|
||||
// No test here calls TestConnection, so a fake repository (no real SQL) is enough
|
||||
// and removes any latent localhost-connection risk.
|
||||
return new GalaxyRepositoryGrpcService(
|
||||
new GalaxyRepository(options),
|
||||
new FakeGalaxyRepository(
|
||||
Array.Empty<GalaxyHierarchyRow>(),
|
||||
Array.Empty<GalaxyAttributeRow>(),
|
||||
deployTime: null),
|
||||
new StubGalaxyHierarchyCache(entry),
|
||||
new RecordingDeployNotifier(),
|
||||
notifier ?? new RecordingDeployNotifier(),
|
||||
scope);
|
||||
}
|
||||
|
||||
@@ -158,6 +218,20 @@ public sealed class GalaxyRepositoryGrpcServiceScopeTests
|
||||
public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Records every <see cref="DeployEvent"/> the service streams.</summary>
|
||||
private sealed class CapturingStreamWriter : IServerStreamWriter<DeployEvent>
|
||||
{
|
||||
public List<DeployEvent> Written { get; } = [];
|
||||
|
||||
public WriteOptions? WriteOptions { get; set; }
|
||||
|
||||
public Task WriteAsync(DeployEvent message)
|
||||
{
|
||||
Written.Add(message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Minimal in-memory <see cref="ServerCallContext"/> for direct service unit tests.</summary>
|
||||
private sealed class TestServerCallContext : ServerCallContext
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user