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;
|
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
|
// ShouldAutoRebuild — pure function gating the auto-heal path
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue