From 8a4f5d5027037a8479a7562950c546885057a5fc Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 12:01:24 +0100 Subject: [PATCH 1/6] Fixing context lifetime for scoped nodes. --- ...teLightTransitionReactiveNodeConfigurator.Cycle.cs | 11 +++++++---- .../LightTransitionReactiveNodeConfigurator.Cycle.cs | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs index cf08992..4eff034 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.Cycle.cs @@ -52,10 +52,13 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg configure(compositeCycleConfigurator); configurators.ForEach(kvp => kvp.Value.AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurators[kvp.Key].CycleNodeFactories.Select(tuple => { - var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.Light); - var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); - var valueIsActiveFunc = () => tuple.matchesNodeState(context); + var factory = new Func>(() => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, kvp.Value.Light); + return new ScopedNode(serviceScope, tuple.nodeFactory(context)); + }); + var valueIsActiveFunc = () => tuple.matchesNodeState(new LightPipelineContext(serviceProvider, kvp.Value.Light)); return (factory, valueIsActiveFunc); })))); return this; diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs index 019de03..fd6ce52 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.Cycle.cs @@ -51,10 +51,13 @@ public ILightTransitionReactiveNodeConfigurator AddCycle(IObservable trigg configure(cycleConfigurator); AddNodeSource(triggerObservable.ToCycleObservable(cycleConfigurator.CycleNodeFactories.Select(tuple => { - var serviceScope = serviceProvider.CreateScope(); - var context = new LightPipelineContext(serviceScope.ServiceProvider, Light); - var factory = new Func>(() => new ScopedNode(serviceScope, tuple.nodeFactory(context))); - var valueIsActiveFunc = () => tuple.matchesNodeState(context); + var factory = new Func>(() => + { + var serviceScope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(serviceScope.ServiceProvider, Light); + return new ScopedNode(serviceScope, tuple.nodeFactory(context)); + }); + var valueIsActiveFunc = () => tuple.matchesNodeState(new LightPipelineContext(serviceProvider, Light)); return (factory, valueIsActiveFunc); }))); return this; From 05fba0c49604a2ce320913f15f48835f57087f4a Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 15:54:10 +0100 Subject: [PATCH 2/6] Using correct scope in when adding a node source. --- ...LightTransitionReactiveNodeConfigurator.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs index ad02ac1..8578c82 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.cs @@ -1,12 +1,15 @@ -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using CodeCasa.Abstractions; +using CodeCasa.Abstractions; using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.AutomationPipelines.Lights.Nodes; using CodeCasa.AutomationPipelines.Lights.Pipeline; using CodeCasa.Lights; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Xml.Linq; +using CodeCasa.AutomationPipelines.Lights.Utils; +using Microsoft.Extensions.DependencyInjection; namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; @@ -99,7 +102,18 @@ public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable public ILightTransitionReactiveNodeConfigurator AddNodeSource(IObservable?>> nodeFactorySource) { - return AddNodeSource(nodeFactorySource.Select(f => f(new LightPipelineContext(serviceProvider, Light)))); + return AddNodeSource(nodeFactorySource.Select(nodeFactory => + { + var scope = serviceProvider.CreateScope(); + var context = new LightPipelineContext(scope.ServiceProvider, Light); + var node = nodeFactory(context); + if (node != null) + { + return new ScopedNode(scope, node); + } + scope.Dispose(); + return null; + })); } /// From d0215de0888e6cf888dad9233dde14d23f5b90c9 Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 16:00:54 +0100 Subject: [PATCH 3/6] Implemented unit tests for reactive node. --- ...sa.AutomationPipelines.Lights.Tests.csproj | 1 + .../ReactiveNodeTests.cs | 287 ++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs diff --git a/tests/CodeCasa.AutomationPipelines.Lights.Tests/CodeCasa.AutomationPipelines.Lights.Tests.csproj b/tests/CodeCasa.AutomationPipelines.Lights.Tests/CodeCasa.AutomationPipelines.Lights.Tests.csproj index d8c591c..66182e8 100644 --- a/tests/CodeCasa.AutomationPipelines.Lights.Tests/CodeCasa.AutomationPipelines.Lights.Tests.csproj +++ b/tests/CodeCasa.AutomationPipelines.Lights.Tests/CodeCasa.AutomationPipelines.Lights.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs new file mode 100644 index 0000000..657b12b --- /dev/null +++ b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs @@ -0,0 +1,287 @@ +using CodeCasa.AutomationPipelines.Lights.Nodes; +using System; +using System.Collections.Generic; +using System.Text; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using Microsoft.Extensions.DependencyInjection; +using System.Reactive.Concurrency; +using CodeCasa.Lights; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using Microsoft.Extensions.Logging; +using System.Linq; +using Moq; +using System.Reactive.Subjects; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines; +using ReactiveNodeClass = CodeCasa.AutomationPipelines.Lights.ReactiveNode.ReactiveNode; + +namespace CodeCasa.AutomationPipelines.Lights.Tests +{ + [TestClass] + public sealed class ReactiveNodeTests + { + private Mock _serviceProviderMock = null!; + private IScheduler _scheduler = null!; + private ReactiveNodeFactory _reactiveNodeFactory = null!; + private Mock _lightMock = null!; + private LightPipelineContextProvider _contextProvider = null!; + private Mock _scopeFactoryMock = null!; + private Mock _scopeMock = null!; + private Mock _scopedServiceProviderMock = null!; + + [TestInitialize] + public void Initialize() + { + _serviceProviderMock = new Mock(); + _scheduler = Scheduler.Immediate; + _reactiveNodeFactory = new ReactiveNodeFactory(_serviceProviderMock.Object, _scheduler); + + var pipelineLoggerMock = new Mock>>(); + var lightPipelineFactory = new LightPipelineFactory(pipelineLoggerMock.Object, _serviceProviderMock.Object, _reactiveNodeFactory); + + _serviceProviderMock.Setup(x => x.GetService(typeof(LightPipelineFactory))) + .Returns(lightPipelineFactory); + + var reactiveNodeLoggerMock = new Mock>(); + _serviceProviderMock.Setup(x => x.GetService(typeof(ILogger))) + .Returns(reactiveNodeLoggerMock.Object); + + _serviceProviderMock.Setup(x => x.GetService(typeof(IScheduler))) + .Returns(_scheduler); + + _lightMock = new Mock(); + _lightMock.Setup(l => l.Id).Returns("test_light"); + _lightMock.Setup(l => l.GetParameters()).Returns(new LightParameters()); + _lightMock.Setup(l => l.GetChildren()).Returns(Array.Empty()); + + _contextProvider = new LightPipelineContextProvider(); + _serviceProviderMock.Setup(x => x.GetService(typeof(LightPipelineContextProvider))) + .Returns(_contextProvider); + + _scopeFactoryMock = new Mock(); + _scopeMock = new Mock(); + _scopedServiceProviderMock = new Mock(); + + _serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))) + .Returns(_scopeFactoryMock.Object); + + _scopeFactoryMock.Setup(x => x.CreateScope()) + .Returns(_scopeMock.Object); + + _scopeMock.Setup(x => x.ServiceProvider) + .Returns(_scopedServiceProviderMock.Object); + + _scopedServiceProviderMock.Setup(x => x.GetService(typeof(LightPipelineContextProvider))) + .Returns(_contextProvider); + } + + [TestMethod] + public void CreateNode() + { + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.SetName("TestNode"); + }); + + // Assert + Assert.IsNotNull(node); + } + + [TestMethod] + public void On_Triggered_AppliesTransition() + { + // Arrange + var triggerSubject = new Subject(); + var expectedParameters = new LightParameters { Brightness = 50 }; + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject, expectedParameters); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + triggerSubject.OnNext(1); + + // Assert + Assert.IsNotNull(lastOutput); + Assert.AreEqual(expectedParameters.Brightness, lastOutput.LightParameters.Brightness); + } + + [TestMethod] + public void On_Triggered_ActivatesNode_Generic() + { + // Arrange + var triggerSubject = new Subject(); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + triggerSubject.OnNext(1); + + // Assert + Assert.IsNotNull(lastOutput); + Assert.AreEqual(100, lastOutput.LightParameters.Brightness); + } + + [TestMethod] + public void On_Triggered_ReplacesAndDisposesOldNode() + { + // Arrange + var triggerSubject = new Subject(); + + var trackerMock = new Mock(); + _serviceProviderMock.Setup(x => x.GetService(typeof(ILifecycleTracker))) + .Returns(trackerMock.Object); + _scopedServiceProviderMock.Setup(x => x.GetService(typeof(ILifecycleTracker))) + .Returns(trackerMock.Object); + + var createdIds = new List(); + var disposedIds = new List(); + + trackerMock.Setup(x => x.Created(It.IsAny())) + .Callback(id => createdIds.Add(id)); + trackerMock.Setup(x => x.Disposed(It.IsAny())) + .Callback(id => disposedIds.Add(id)); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject); + }); + + // Trigger 1 + triggerSubject.OnNext(1); + + // Assert 1 + Assert.AreEqual(1, createdIds.Count, "Should have created one node"); + Assert.AreEqual(0, disposedIds.Count, "Should not have disposed any node yet"); + + // Trigger 2 + triggerSubject.OnNext(2); + + // Assert 2 + Assert.AreEqual(2, createdIds.Count, "Should have created second node"); + Assert.AreEqual(1, disposedIds.Count, "Should have disposed one node"); + Assert.AreEqual(createdIds[0], disposedIds[0], "Should have disposed the first node"); + } + + [TestMethod] + public void On_Triggered_ActivatesNode_WithContext() + { + // Arrange + var triggerSubject = new Subject(); + + _serviceProviderMock.Setup(x => x.GetService(typeof(ILightPipelineContext))) + .Returns(() => _contextProvider.GetLightPipelineContext()); + _scopedServiceProviderMock.Setup(x => x.GetService(typeof(ILightPipelineContext))) + .Returns(() => _contextProvider.GetLightPipelineContext()); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + triggerSubject.OnNext(1); + + // Assert + Assert.IsNotNull(lastOutput); + Assert.AreEqual(100, lastOutput.LightParameters.Brightness); + } + + public class TestPipelineNode : PipelineNode + { + public TestPipelineNode() + { + Output = new LightParameters { Brightness = 100 }.AsTransition(); + } + } + + public interface ILifecycleTracker + { + void Created(Guid id); + void Disposed(Guid id); + } + + public class LifecycleTrackingPipelineNode : PipelineNode, IDisposable + { + private readonly ILifecycleTracker _tracker; + public Guid Id { get; } = Guid.NewGuid(); + + public LifecycleTrackingPipelineNode(ILifecycleTracker tracker) + { + _tracker = tracker; + _tracker.Created(Id); + Output = new LightParameters { Brightness = 100 }.AsTransition(); + } + + public void Dispose() + { + _tracker.Disposed(Id); + } + } + + public class ContextAwarePipelineNode : PipelineNode + { + public ContextAwarePipelineNode(ILightPipelineContext context) + { + if (context.Light.Id == "test_light") + { + Output = new LightParameters { Brightness = 100 }.AsTransition(); + } + } + } + + [TestMethod] + public void On_Triggered_CreatesScopedServiceProvider_AndDisposesIt() + { + // Arrange + var triggerSubject = new Subject(); + + // Allow resolving IServiceProvider from itself + _scopedServiceProviderMock.Setup(x => x.GetService(typeof(IServiceProvider))) + .Returns(_scopedServiceProviderMock.Object); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject); + }); + + // Trigger 1 + triggerSubject.OnNext(1); + + // Assert 1 + _scopeFactoryMock.Verify(x => x.CreateScope(), Times.Once, "Scope should be created on trigger"); + _scopeMock.As().Verify(x => x.DisposeAsync(), Times.Never, "Scope should not be disposed yet"); + + // Trigger 2 + triggerSubject.OnNext(2); + + // Assert 2 + _scopeFactoryMock.Verify(x => x.CreateScope(), Times.Exactly(2), "New scope should be created on second trigger"); + _scopeMock.As().Verify(x => x.DisposeAsync(), Times.Once, "Old scope should be disposed"); + } + + public class ScopedPipelineNode : PipelineNode + { + public ScopedPipelineNode(IServiceProvider serviceProvider) + { + Output = new LightParameters { Brightness = 100 }.AsTransition(); + } + } + } +} From 2e158c638849d7a218702c88143aff580d45798e Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 16:04:49 +0100 Subject: [PATCH 4/6] Updated unit tests --- .../ReactiveNodeTests.cs | 83 +++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs index 657b12b..091839d 100644 --- a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs +++ b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs @@ -1,18 +1,13 @@ using CodeCasa.AutomationPipelines.Lights.Nodes; -using System; -using System.Collections.Generic; -using System.Text; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; using Microsoft.Extensions.DependencyInjection; using System.Reactive.Concurrency; using CodeCasa.Lights; using CodeCasa.AutomationPipelines.Lights.Pipeline; using Microsoft.Extensions.Logging; -using System.Linq; using Moq; using System.Reactive.Subjects; using CodeCasa.AutomationPipelines.Lights.Context; -using CodeCasa.AutomationPipelines; using ReactiveNodeClass = CodeCasa.AutomationPipelines.Lights.ReactiveNode.ReactiveNode; namespace CodeCasa.AutomationPipelines.Lights.Tests @@ -276,6 +271,84 @@ public void On_Triggered_CreatesScopedServiceProvider_AndDisposesIt() _scopeMock.As().Verify(x => x.DisposeAsync(), Times.Once, "Old scope should be disposed"); } + [TestMethod] + public void TurnOffWhen_Triggered_EmitsOffAndPassesThrough() + { + // Arrange + var triggerSubject = new Subject(); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.TurnOffWhen(triggerSubject); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + // Trigger + triggerSubject.OnNext(1); + + // Assert + Assert.IsNotNull(lastOutput); + Assert.AreEqual(LightTransition.Off(), lastOutput); + + // Verify pass-through behavior + var inputTransition = new LightParameters { Brightness = 100 }.AsTransition(); + node.Input = inputTransition; + Assert.AreEqual(inputTransition, lastOutput); + } + + [TestMethod] + public void PassThroughOn_Triggered_PassesThrough() + { + // Arrange + var triggerSubject = new Subject(); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.PassThroughOn(triggerSubject); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + // Trigger + triggerSubject.OnNext(1); + + // Verify pass-through behavior + var inputTransition = new LightParameters { Brightness = 100 }.AsTransition(); + node.Input = inputTransition; + Assert.AreEqual(inputTransition, lastOutput); + } + + [TestMethod] + public void Input_PropagatedToActiveNode() + { + // Arrange + var triggerSubject = new Subject(); + + // Act + var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + { + config.On(triggerSubject, context => new FactoryNode(input => + input with { LightParameters = new LightParameters { Brightness = 100 } })); + }); + + LightTransition? lastOutput = null; + node.OnNewOutput.Subscribe(output => lastOutput = output); + + triggerSubject.OnNext(1); // Activate the node + + // Act + node.Input = new LightTransition { LightParameters = new LightParameters { Brightness = 10 } }; + + // Assert + Assert.IsNotNull(lastOutput); + Assert.AreEqual(100, lastOutput.LightParameters.Brightness); + } + public class ScopedPipelineNode : PipelineNode { public ScopedPipelineNode(IServiceProvider serviceProvider) From ce0e46d7a0627ad690b3fc7aae381eb9e879d355 Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 16:16:43 +0100 Subject: [PATCH 5/6] Fixed warnings in tests. --- .../ReactiveNodeTests.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs index 091839d..baba68a 100644 --- a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs +++ b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs @@ -149,7 +149,7 @@ public void On_Triggered_ReplacesAndDisposesOldNode() .Callback(id => disposedIds.Add(id)); // Act - var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + _ = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => { config.On(triggerSubject); }); @@ -158,15 +158,15 @@ public void On_Triggered_ReplacesAndDisposesOldNode() triggerSubject.OnNext(1); // Assert 1 - Assert.AreEqual(1, createdIds.Count, "Should have created one node"); - Assert.AreEqual(0, disposedIds.Count, "Should not have disposed any node yet"); + Assert.HasCount(1, createdIds, "Should have created one node"); + Assert.HasCount(0, disposedIds, "Should not have disposed any node yet"); // Trigger 2 triggerSubject.OnNext(2); // Assert 2 - Assert.AreEqual(2, createdIds.Count, "Should have created second node"); - Assert.AreEqual(1, disposedIds.Count, "Should have disposed one node"); + Assert.HasCount(2, createdIds, "Should have created second node"); + Assert.HasCount(1, disposedIds, "Should have disposed one node"); Assert.AreEqual(createdIds[0], disposedIds[0], "Should have disposed the first node"); } @@ -251,7 +251,7 @@ public void On_Triggered_CreatesScopedServiceProvider_AndDisposesIt() .Returns(_scopedServiceProviderMock.Object); // Act - var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => + _ = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => { config.On(triggerSubject); }); @@ -332,8 +332,9 @@ public void Input_PropagatedToActiveNode() // Act var node = _reactiveNodeFactory.CreateReactiveNode(_lightMock.Object, config => { - config.On(triggerSubject, context => new FactoryNode(input => - input with { LightParameters = new LightParameters { Brightness = 100 } })); + config.On(triggerSubject, _ => new FactoryNode(input => input == null + ? null + : input with { LightParameters = new LightParameters { Brightness = 100 } })); }); LightTransition? lastOutput = null; @@ -351,7 +352,7 @@ public void Input_PropagatedToActiveNode() public class ScopedPipelineNode : PipelineNode { - public ScopedPipelineNode(IServiceProvider serviceProvider) + public ScopedPipelineNode() { Output = new LightParameters { Brightness = 100 }.AsTransition(); } From d3b16afa864d1102f248a0e903c42add42ddb8cb Mon Sep 17 00:00:00 2001 From: Jasper Date: Sat, 3 Jan 2026 16:36:20 +0100 Subject: [PATCH 6/6] Fixed unit tests --- .../ReactiveNodeTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs index baba68a..538b161 100644 --- a/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs +++ b/tests/CodeCasa.AutomationPipelines.Lights.Tests/ReactiveNodeTests.cs @@ -55,6 +55,7 @@ public void Initialize() _scopeFactoryMock = new Mock(); _scopeMock = new Mock(); + _scopeMock.As(); _scopedServiceProviderMock = new Mock(); _serviceProviderMock.Setup(x => x.GetService(typeof(IServiceScopeFactory))) @@ -68,6 +69,9 @@ public void Initialize() _scopedServiceProviderMock.Setup(x => x.GetService(typeof(LightPipelineContextProvider))) .Returns(_contextProvider); + + _scopedServiceProviderMock.Setup(x => x.GetService(typeof(IScheduler))) + .Returns(_scheduler); } [TestMethod]