Resolve all 46 low-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 22:56:36 -04:00
parent fef3e7675f
commit 9ee402bbcd
139 changed files with 2621 additions and 1082 deletions
@@ -125,6 +125,9 @@ public class TopologyPageTests : BunitContext
.Returns(Task.FromResult<IReadOnlyList<Template>>(templates?.ToList() ?? new List<Template>()));
_repo.GetAllInstancesAsync(Arg.Any<CancellationToken>())
.Returns(Task.FromResult<IReadOnlyList<Instance>>(instances?.ToList() ?? new List<Instance>()));
_repo.GetTopologyInstancesAsync(Arg.Any<int>(), Arg.Any<int?>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(call => (IReadOnlyList<Instance>)(instances ?? []).Where(i => i.SiteId == call.ArgAt<int>(0) && i.AreaId == call.ArgAt<int?>(1))
.Skip(call.ArgAt<int>(2)).Take(call.ArgAt<int>(3)).ToList());
areasBySite ??= new();
_repo.GetAreasBySiteIdAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
@@ -137,6 +140,94 @@ public class TopologyPageTests : BunitContext
});
}
private IRenderedComponent<TopologyPage> RenderLoadedTopology()
{
var cut = Render<TopologyPage>();
cut.InvokeAsync(async () =>
{
foreach (var site in await _siteRepo.GetAllSitesAsync())
{
await cut.Instance.LoadBranchAsync(site.Id, null);
foreach (var area in await _repo.GetAreasBySiteIdAsync(site.Id))
await cut.Instance.LoadBranchAsync(site.Id, area.Id);
}
cut.Render();
}).GetAwaiter().GetResult();
return cut;
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task BranchAndRefresh_SerializeRepositoryReadsWithoutDroppingUserPage(bool branchFirst)
{
SeedRepos(sites: [new Site("Plant", "plant") { Id = 1 }],
instances: [new Instance("First") { Id = 1, SiteId = 1 }]);
var cut = Render<TopologyPage>();
await cut.InvokeAsync(() => cut.Instance.LoadBranchAsync(1, null));
var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var release = new TaskCompletionSource<IReadOnlyList<Instance>>(TaskCreationOptions.RunContinuationsAsynchronously);
var calls = 0;
_repo.GetTopologyInstancesAsync(1, null, Arg.Any<int>(), 50, Arg.Any<CancellationToken>())
.Returns(call =>
{
if (Interlocked.Increment(ref calls) == 1)
{
entered.TrySetResult();
return release.Task;
}
return Task.FromResult<IReadOnlyList<Instance>>([new Instance("Next") { Id = 51, SiteId = 1 }]);
});
var first = cut.InvokeAsync(() => branchFirst
? cut.Instance.LoadBranchAsync(1, null, 50) : cut.Instance.LoadDeployedStateAsync());
await entered.Task.WaitAsync(TimeSpan.FromSeconds(2));
Task second = Task.CompletedTask;
await cut.InvokeAsync(() =>
{
second = branchFirst ? cut.Instance.LoadDeployedStateAsync() : cut.Instance.LoadBranchAsync(1, null, 50);
});
try
{
Assert.False(second.IsCompleted);
Assert.Equal(1, Volatile.Read(ref calls));
}
finally
{
release.TrySetResult([new Instance("Next") { Id = 51, SiteId = 1 }]);
await Task.WhenAll(first, second).WaitAsync(TimeSpan.FromSeconds(2));
}
Assert.Equal(2, calls);
var field = typeof(TopologyPage).GetField("_allInstances", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
Assert.Equal(51, Assert.Single((List<Instance>)field.GetValue(cut.Instance)!).Id);
}
[Fact]
public async Task BranchPages_AreLazyAndReplacePreviousPage()
{
SeedRepos(sites: [new Site("Plant", "plant") { Id = 1 }],
instances: Enumerable.Range(1, 101).Select(id => new Instance($"Pump-{id}") { Id = id, SiteId = 1 }));
var cut = Render<TopologyPage>();
await cut.InvokeAsync(() => cut.Instance.LoadBranchAsync(1, null));
var field = typeof(TopologyPage).GetField("_allInstances", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
Assert.Equal(50, ((List<Instance>)field.GetValue(cut.Instance)!).Count);
await cut.InvokeAsync(() => cut.Instance.LoadBranchAsync(1, null, 50));
var second = (List<Instance>)field.GetValue(cut.Instance)!;
Assert.Equal(50, second.Count);
Assert.Equal(51, second[0].Id);
await _repo.DidNotReceive().GetAllInstancesAsync(Arg.Any<CancellationToken>());
}
[Fact]
public void InitialLoad_DoesNotReadInstancesOrTemplates()
{
SeedRepos(sites: [new Site("Plant", "plant") { Id = 1 }]);
_repo.ClearReceivedCalls();
var cut = Render<TopologyPage>();
cut.WaitForAssertion(() => Assert.Contains("Plant", cut.Markup));
_repo.DidNotReceive().GetAllInstancesAsync(Arg.Any<CancellationToken>());
_repo.DidNotReceive().GetAllTemplatesAsync(Arg.Any<CancellationToken>());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
@@ -145,7 +236,7 @@ public class TopologyPageTests : BunitContext
var instance = new Instance("Pump") { Id = 100, SiteId = 1, AreaId = 10 };
SeedRepos(sites: [new Site("Plant", "plant") { Id = 1 }], instances: [instance]);
_repo.GetInstanceByIdAsync(100, Arg.Any<CancellationToken>()).Returns(instance);
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
var auth = Services.GetRequiredService<AuthenticationStateProvider>();
var identity = (ClaimsIdentity)(await auth.GetAuthenticationStateAsync()).User.Identity!;
if (revoked) identity.AddClaim(new Claim(JwtTokenService.SiteIdClaimType, "2"));
@@ -164,7 +255,7 @@ public class TopologyPageTests : BunitContext
public async Task UI13_ExistingTopologyCreateUsesCurrentSiteScope(bool revoked)
{
SeedRepos(sites: [new Site("Plant", "plant") { Id = 1 }]);
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
var auth = Services.GetRequiredService<AuthenticationStateProvider>();
var identity = (ClaimsIdentity)(await auth.GetAuthenticationStateAsync()).User.Identity!;
if (revoked) identity.AddClaim(new Claim(JwtTokenService.SiteIdClaimType, "2"));
@@ -181,7 +272,7 @@ public class TopologyPageTests : BunitContext
{
SeedRepos();
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
Assert.Contains("No sites configured", cut.Markup);
}
@@ -193,7 +284,7 @@ public class TopologyPageTests : BunitContext
// under it must still appear so users can move/create into it.
SeedRepos(sites: new[] { new Site("Plant-A", "plant-a") { Id = 1 } });
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
Assert.Contains("Plant-A", cut.Markup);
Assert.Contains("bi-building", cut.Markup);
@@ -222,7 +313,7 @@ public class TopologyPageTests : BunitContext
},
areasBySite: areasBySite);
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
// Expand the site, then the area, to render the instance leaf. The
// helper scopes by the row's own label so we don't match outer rows
@@ -251,7 +342,7 @@ public class TopologyPageTests : BunitContext
sites: new[] { new Site("Plant-A", "plant-a") { Id = 1 } },
areasBySite: areasBySite);
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
FindToggleForLabel(cut, "Plant-A")!.Click();
var search = cut.Find("input[type='text']");
@@ -287,7 +378,7 @@ public class TopologyPageTests : BunitContext
new Site("Plant-B", "plant-b") { Id = 2 },
});
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
// The permitted site is rendered; the non-permitted site is not.
Assert.Contains("Plant-A", cut.Markup);
@@ -304,7 +395,7 @@ public class TopologyPageTests : BunitContext
new Site("Plant-B", "plant-b") { Id = 2 },
});
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
Assert.Contains("Plant-A", cut.Markup);
Assert.Contains("Plant-B", cut.Markup);
@@ -319,7 +410,7 @@ public class TopologyPageTests : BunitContext
};
SeedRepos(sites: new[] { new Site("Plant-A", "plant-a") { Id = 1 } }, areasBySite: areasBySite);
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
FindToggleForLabel(cut, "Plant-A")!.Click();
var areaLabel = cut.FindAll("span.tv-label").First(s => s.TextContent == "Line-1");
@@ -342,7 +433,7 @@ public class TopologyPageTests : BunitContext
new Instance("Pump-001") { Id = 100, SiteId = 1, State = InstanceState.NotDeployed }
});
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
FindToggleForLabel(cut, "Plant-A")!.Click();
var instanceLabel = cut.FindAll("span.tv-label").First(s => s.TextContent == "Pump-001");
@@ -410,7 +501,7 @@ public class TopologyPageTests : BunitContext
.Returns(Task.FromResult(Result<FlatteningPipelineResult>.Success(
new FlatteningPipelineResult(currentConfig, "hash-new", ValidationResult.Success()))));
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
FindToggleForLabel(cut, "Plant-A")!.Click();
FindToggleForLabel(cut, "Line-1")!.Click();
@@ -472,7 +563,7 @@ public class TopologyPageTests : BunitContext
// call count is the proxy for "did staleness recompute".
SeedOneDeployedInstanceWithComparison();
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
// Initial load computed staleness exactly once.
await _deployRepo.Received(1)
@@ -493,7 +584,7 @@ public class TopologyPageTests : BunitContext
// cheap deployed state (hierarchy + State badge) must still render.
SeedOneDeployedInstanceWithComparison();
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
FindToggleForLabel(cut, "Plant-A")!.Click();
Assert.Contains("Pump-001", cut.Markup);
@@ -510,7 +601,7 @@ public class TopologyPageTests : BunitContext
// again — a second snapshot-repository call.
SeedOneDeployedInstanceWithComparison();
var cut = Render<TopologyPage>();
var cut = RenderLoadedTopology();
_deployRepo.Received(1)
.GetDeployedSnapshotByInstanceIdAsync(100, Arg.Any<CancellationToken>());