test(engine): cover RebuildFinder failure + success paths
All checks were successful
CI / build-and-test (push) Successful in 27s
All checks were successful
CI / build-and-test (push) Successful in 27s
Three new tests around the finder rebuild:
- Failure path: CreateFinder throws on the 2nd call. RebuildFinder
returns false, keeps the original (non-disposed) finder, and a
follow-up PollOnce still reads sources. Pre-fix this regressed:
the incumbent was disposed before the throw, so PollOnce hit a
disposed handle (now an ObjectDisposedException from the fake).
- Success path: RebuildFinder returns true, builds exactly one
replacement, and re-emits all currently-visible sources as Added
(seen-set cleared).
- Failure path preserves the seen-set: no spurious Added re-fires
for sources that were already known.
This commit is contained in:
parent
886e20e501
commit
e36b928c69
1 changed files with 80 additions and 0 deletions
|
|
@ -67,6 +67,86 @@ public class NdiDiscoveryServiceTests
|
|||
return list;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RebuildFinder — swap-then-dispose, failure keeps the old finder
|
||||
// ============================================================
|
||||
|
||||
[Fact]
|
||||
public void RebuildFinder_WhenCreateFails_KeepsExistingFinder_AndKeepsPolling()
|
||||
{
|
||||
// First finder (construction) succeeds; the rebuild's CreateFinder throws.
|
||||
var interop = new FakeNdiInterop
|
||||
{
|
||||
CreateFinderHook = ordinal =>
|
||||
{
|
||||
if (ordinal >= 2) throw new InvalidOperationException("runtime refused a new finder");
|
||||
},
|
||||
};
|
||||
interop.Sources.Add("PC1 (Teams - Jane)");
|
||||
var channel = Channel.CreateUnbounded<DiscoveryEvent>();
|
||||
var svc = new NdiDiscoveryService(interop, channel.Writer, NullLogger<NdiDiscoveryService>.Instance);
|
||||
|
||||
// Prime the seen-set.
|
||||
svc.PollOnce();
|
||||
DrainChannel(channel.Reader);
|
||||
|
||||
// Rebuild fails — must return false and must NOT dispose the live finder.
|
||||
svc.RebuildFinder("test: simulated failure").Should().BeFalse();
|
||||
interop.FinderCreatedCount.Should().Be(2); // initial + the failed attempt
|
||||
|
||||
// The incumbent finder is still alive: PollOnce reads it without throwing.
|
||||
// Pre-fix this threw ObjectDisposedException because the finder had been
|
||||
// disposed before the failing CreateFinder call.
|
||||
var poll = () => svc.PollOnce();
|
||||
poll.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RebuildFinder_FailureKeepsSeenSet_NoSpuriousReAdd()
|
||||
{
|
||||
var interop = new FakeNdiInterop
|
||||
{
|
||||
CreateFinderHook = ordinal =>
|
||||
{
|
||||
if (ordinal >= 2) throw new InvalidOperationException("boom");
|
||||
},
|
||||
};
|
||||
interop.Sources.Add("PC1 (Teams - Jane)");
|
||||
var channel = Channel.CreateUnbounded<DiscoveryEvent>();
|
||||
var svc = new NdiDiscoveryService(interop, channel.Writer, NullLogger<NdiDiscoveryService>.Instance);
|
||||
|
||||
svc.PollOnce();
|
||||
DrainChannel(channel.Reader); // consume the initial Added
|
||||
|
||||
svc.RebuildFinder("test: simulated failure").Should().BeFalse();
|
||||
svc.PollOnce();
|
||||
|
||||
// Seen-set was preserved across the failed rebuild, so the already-known
|
||||
// source must NOT re-fire as Added.
|
||||
DrainChannel(channel.Reader).Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RebuildFinder_OnSuccess_SwapsFinder_AndReEmitsKnownSources()
|
||||
{
|
||||
var interop = new FakeNdiInterop();
|
||||
interop.Sources.Add("PC1 (Teams - Jane)");
|
||||
var channel = Channel.CreateUnbounded<DiscoveryEvent>();
|
||||
var svc = new NdiDiscoveryService(interop, channel.Writer, NullLogger<NdiDiscoveryService>.Instance);
|
||||
|
||||
svc.PollOnce();
|
||||
DrainChannel(channel.Reader); // consume initial Added
|
||||
|
||||
svc.RebuildFinder("test: success").Should().BeTrue();
|
||||
interop.FinderCreatedCount.Should().Be(2);
|
||||
|
||||
// Seen-set was cleared on success, so the still-visible source re-fires.
|
||||
svc.PollOnce();
|
||||
DrainChannel(channel.Reader).OfType<DiscoveryEvent.Added>()
|
||||
.Select(a => a.Source.FullName)
|
||||
.Should().BeEquivalentTo(new[] { "PC1 (Teams - Jane)" });
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ShouldAutoRebuild — pure function gating the auto-heal path
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue