diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index 515f21828..6e338e19a 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -7,6 +7,7 @@ using JoySoftware.HomeAssistant.Client; using Moq; using NetDaemon.Common; +using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Storage; using Xunit; @@ -24,8 +25,12 @@ public partial class DaemonHostTestBase : IAsyncLifetime private readonly HassClientMock _defaultHassClientMock; private readonly HttpHandlerMock _defaultHttpHandlerMock; private readonly LoggerMock _loggerMock; + private Task? _fakeConnectedDaemon; - internal DaemonHostTestBase() + /// + /// Default contructor + /// + public DaemonHostTestBase() { _loggerMock = new LoggerMock(); _defaultHassClientMock = HassClientMock.DefaultMock; @@ -38,6 +43,7 @@ internal DaemonHostTestBase() _loggerMock.LoggerFactory, _defaultHttpHandlerMock.Object); + _defaultDaemonHost.InternalDelayTimeForTts = 0; // Allow no extra waittime } /// @@ -61,7 +67,10 @@ internal DaemonHostTestBase() /// public LoggerMock LoggerMock => _loggerMock; - Task IAsyncLifetime.DisposeAsync() + /// + /// Cleans up test + /// + public Task DisposeAsync() { return Task.CompletedTask; } @@ -81,9 +90,9 @@ public dynamic GetDynamicDataObject(string testData = "testdata") /// /// Converts parameters to dynamics /// - public (dynamic, ExpandoObject) GetDynamicObject(params (string, object)[] dynamicParameters) + public (dynamic, FluentExpandoObject) GetDynamicObject(params (string, object)[] dynamicParameters) { - var expandoObject = new ExpandoObject(); + var expandoObject = new FluentExpandoObject(); var dict = expandoObject as IDictionary; foreach (var (name, value) in dynamicParameters) @@ -101,16 +110,44 @@ public virtual Task InitializeAsync() return Task.CompletedTask; } + /// + /// Sets fake current state of entity, adds it if entity not exists + /// + /// The state to sett + public void SetEntityState(HassState state) + { + DefaultHassClientMock.FakeStates[state.EntityId] = state; + } + + /// + /// Sets fake current state of entity, adds it if entity not exists + /// + /// Unique id of entity + /// State to set + /// Area of entity + public void SetEntityState(string entityId, dynamic? state = null, string? area = null) + { + var entity = new EntityState + { + EntityId = entityId, + Area = area, + State = state + }; + + DefaultDaemonHost.InternalState[entityId] = entity; + } + /// /// Adds an new instance of app /// /// The instance of the app to add - public void AddAppInstance(INetDaemonAppBase app) + public async Task AddAppInstance(INetDaemonAppBase app) { if (string.IsNullOrEmpty(app.Id)) - throw new ArgumentException("Application needs an unique id, please provide it!"); + app.Id = Guid.NewGuid().ToString(); DefaultDaemonHost.InternalAllAppInstances[app.Id] = app; DefaultDaemonHost.InternalRunningAppInstances[app.Id] = app; + await app.StartUpAsync(DefaultDaemonHost); } /// @@ -127,10 +164,24 @@ public void AddChangedEvent(string entityId, object fromState, object toState) /// /// Adds a full home assistant fake event /// - /// Event to fake - public void AddChangedEvent(HassEvent hassEvent) + /// + /// + public void AddChangeEvent(HassState? fromState, HassState? toState) { - DefaultHassClientMock.FakeEvents.Enqueue(hassEvent); + DefaultHassClientMock.AddChangeEventFull(fromState, toState); + } + + /// + /// Adds an simple state change event to NetDaemon to trigger apps + /// + /// Unique id of the entity + /// From state + /// To state + /// Last updated time + /// Last changed time + public void AddChangedEvent(string entityId, object fromState, object toState, DateTime lastUpdated, DateTime lastChanged) + { + DefaultHassClientMock.AddChangedEvent(entityId, fromState, toState, lastUpdated, lastChanged); } /// @@ -153,68 +204,97 @@ public void AddCustomEvent(string eventType, dynamic? data) /// Domain of event /// Service to call /// Custom data - public void AddCallServiceEvent(string domain, string service, dynamic data) + public void AddCallServiceEvent(string domain, string service, dynamic? data = null) { DefaultHassClientMock.AddCallServiceEvent(domain, service, data); } + /// + /// Verifies that a custom event are sent + /// + /// Name of event + public void VerifyEventSent(string ev) + { + DefaultHassClientMock.Verify(n => n.SendEvent(ev, It.IsAny())); + } + + /// + /// Verifies that a custom event are sent + /// + /// Name of event + /// Data sent by event + public void VerifyEventSent(string ev, object? eventData) + { + DefaultHassClientMock.Verify(n => n.SendEvent(ev, eventData)); + } + /// /// Verify that a service has been called /// /// Domain of service /// The service name /// Attributes - public void VerifyCallService(string domain, string service, + public void VerifyCallServiceTuple(string domain, string service, params (string attribute, object value)[] attributesTuples) { - DefaultHassClientMock.VerifyCallService(domain, service, attributesTuples); + DefaultHassClientMock.VerifyCallServiceTuple(domain, service, attributesTuples); } /// - /// Verify that a service been called specific number of times + /// Verifies that call_service is called /// - /// Service name - /// Times called - public void VerifyCallServiceTimes(string service, Times times) + /// Service domain + /// Service to verify + /// Data sent by service + /// If service was waiting for response + /// Number of times called + public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Moq.Times? times = null) { - DefaultHassClientMock.VerifyCallServiceTimes(service, times); + DefaultHassClientMock.VerifyCallService(domain, service, data, waitForResponse, times); } /// - /// Returns default DaemonHost + /// Verifies that call_service is called /// - /// Timeout in ms - /// True if use timeout while debug test - public async Task<(Task, CancellationTokenSource)> ReturnRunningDefauldDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) + /// Service domain + /// Service to verify + /// EntityId to verify + /// Data sent by service + /// If service was waiting for response + /// Number of times called + /// Attributes to verify + public void VerifyCallService(string domain, string service, string entityId, object? data = null, bool waitForResponse = false, Moq.Times? times = null, + params (string attribute, object value)[] attributesTuples) { - await InitApps(); - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - return (_defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token), cancelSource); + var serviceObject = new FluentExpandoObject(); + serviceObject["entity_id"] = entityId; + foreach (var (attr, val) in attributesTuples) + { + serviceObject[attr] = val; + } + + DefaultHassClientMock.VerifyCallService(domain, service, serviceObject, waitForResponse, times); } + /// + /// Verifies that call_service is called + /// + /// Service domain + /// Service to verify + /// If service was waiting for response + public void VerifyCallService(string domain, string service, bool waitForResponse = false) + { + DefaultHassClientMock.VerifyCallService(domain, service, waitForResponse); + } /// - /// Runs the default daemon to process messages until canceled + /// Verify that a service been called specific number of times /// - /// Timeout time - /// True if debugging should cancel - /// - public async Task RunDefaultDaemonUntilCanceled(short milliSeconds = 100, bool overrideDebugNotCancel = false) + /// Service name + /// Times called + public void VerifyCallServiceTimes(string service, Times times) { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - try - { - await InitApps(); - await _defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Expected behaviour - } + DefaultHassClientMock.VerifyCallServiceTimes(service, times); } /// @@ -222,7 +302,7 @@ public async Task RunDefaultDaemonUntilCanceled(short milliSeconds = 100, bool o /// /// Task to wait for /// - public async Task WaitUntilCanceled(Task task) + private async Task WaitUntilCanceled(Task task) { try { @@ -237,7 +317,7 @@ public async Task WaitUntilCanceled(Task task) /// /// Initialize applications /// - public async Task InitApps() + private async Task InitApps() { foreach (var inst in DefaultDaemonHost.InternalAllAppInstances) { @@ -251,13 +331,57 @@ public async Task InitApps() } } + /// + /// Verifies that a state is set + /// + /// Unique identifier of the entity + /// State being set + /// Attributes being set + public void VerifySetState(string entityId, string state, + params (string attribute, object value)[] attributesTuples) + { + DefaultHassClientMock.VerifySetState(entityId, state, attributesTuples); + } + + /// + /// Verifies that state being set + /// + /// Unique identifier of the entity + /// How many times it being set + public void VerifySetStateTimes(string entityId, Times times) + { + DefaultHassClientMock.VerifySetStateTimes(entityId, times); + } + + /// + /// Initialize the fake netdaemon core, must be run in most cases starting a test + /// + /// Timeout (ms) of how long fake daemon will stay connected and process events + /// True if running debug mode should not cancel on timeout + protected async Task FakeDaemonInit(short timeout = 200, bool overrideDebugNotCancel = false) + { + _fakeConnectedDaemon = await GetConnectedNetDaemonTask(timeout, overrideDebugNotCancel); + } + + /// + /// + /// + /// + protected async Task FakeRunDaemonCoreUntilTimeout() + { + _ = _fakeConnectedDaemon ?? + throw new NullReferenceException("No task to process, did you forget to run InitFakeDaemon at the beginning of the test?"); + + await WaitUntilCanceled(_fakeConnectedDaemon).ConfigureAwait(false); + } + /// /// Get already pre-connected mock NetDaemon object /// /// Timeout in milliseconds /// True to use timeout while debugging /// - protected async Task GetConnectedNetDaemonTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) + private async Task GetConnectedNetDaemonTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) { var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel ? new CancellationTokenSource() @@ -270,13 +394,7 @@ protected async Task GetConnectedNetDaemonTask(short milliSeconds = 100, b return daemonTask; } - /// - /// Wait until default NetDaemon has connected - /// - /// Daemon object - /// Cancellation token - /// - protected async Task WaitForDefaultDaemonToConnect(NetDaemonHost daemonHost, CancellationToken cancellationToken) + private async Task WaitForDefaultDaemonToConnect(NetDaemonHost daemonHost, CancellationToken cancellationToken) { var nrOfTimesCheckForConnectedState = 0; diff --git a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs index 750ca0b1e..a96fa7575 100644 --- a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs @@ -17,11 +17,31 @@ namespace NetDaemon.Daemon.Fakes /// public class HassClientMock : Mock { - internal HassAreas Areas = new HassAreas(); - internal HassDevices Devices = new HassDevices(); - internal HassEntities Entities = new HassEntities(); - internal ConcurrentQueue FakeEvents = new(); - internal ConcurrentDictionary FakeStates = new(); + /// + /// Fake areas in HassClient + /// + /// + public HassAreas Areas = new HassAreas(); + + /// + /// Fake devices in HassClient + /// + /// + public HassDevices Devices = new HassDevices(); + + /// + /// Fake entities in HassClient + /// + public HassEntities Entities = new HassEntities(); + + /// + /// Fake events in HassClient + /// + public ConcurrentQueue FakeEvents = new(); + /// + /// All current fake entities and it's states + /// + public ConcurrentDictionary FakeStates = new(); /// /// Default constructor @@ -104,7 +124,7 @@ public static HassClientMock MockConnectFalse /// Domain of service /// Service to fake /// Data sent by service - public void AddCallServiceEvent(string domain, string service, dynamic data) + public void AddCallServiceEvent(string domain, string service, dynamic? data = null) { // Todo: Refactor to smth smarter FakeEvents.Enqueue(new HassEvent @@ -196,6 +216,25 @@ public void AddChangedEvent(string entityId, object fromState, object toState) }); } + /// + /// Adds a changed event for entity + /// + /// The from state advanced details + /// The to state advanced details + public void AddChangeEventFull(HassState? fromState, HassState? toState) + { + FakeEvents.Enqueue(new HassEvent + { + EventType = "state_changed", + Data = new HassStateChangedEventData + { + EntityId = toState?.EntityId ?? fromState?.EntityId ?? "", + NewState = toState, + OldState = fromState + }, + }); + } + /// /// Adds a custom event /// @@ -254,7 +293,7 @@ public CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) /// Service domain /// Service to verify /// Attributes sent by service - public void VerifyCallService(string domain, string service, + public void VerifyCallServiceTuple(string domain, string service, params (string attribute, object value)[] attributesTuples) { var attributes = new FluentExpandoObject(); @@ -264,6 +303,38 @@ public void VerifyCallService(string domain, string service, Verify(n => n.CallService(domain, service, attributes, It.IsAny()), Times.AtLeastOnce); } + /// + /// Verifies that call_service is called + /// + /// Service domain + /// Service to verify + /// Data sent by service + /// If service was waiting for response + /// Number of times called + public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Moq.Times? times = null) + { + if (times is not null) + Verify(n => n.CallService(domain, service, data!, waitForResponse), times.Value); + else + Verify(n => n.CallService(domain, service, data!, waitForResponse), Times.AtLeastOnce); + + } + + /// + /// Verifies that call_service is called + /// + /// Service domain + /// Service to verify + /// If service was waiting for response + /// Number of times called + public void VerifyCallService(string domain, string service, bool waitForResponse = false, Moq.Times? times = null) + { + if (times is not null) + Verify(n => n.CallService(domain, service, It.IsAny(), waitForResponse), times.Value); + else + Verify(n => n.CallService(domain, service, It.IsAny(), waitForResponse), Times.AtLeastOnce); + } + /// /// Verifies service being sent "times" times /// @@ -274,6 +345,8 @@ public void VerifyCallServiceTimes(string service, Times times) Verify(n => n.CallService(It.IsAny(), service, It.IsAny(), It.IsAny()), times); } + + /// /// Verify state if entity /// diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs index 8619c62e7..3f17c1bfa 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs @@ -7,6 +7,7 @@ using NetDaemon.Common.Fluent; using NetDaemon.Daemon.Storage; using Xunit; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.Daemon { diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs b/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs index 8f9225ecd..a44db111c 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs @@ -1,74 +1,74 @@ -using Moq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using NetDaemon.Common; +// using Moq; +// using System.Net; +// using System.Net.Http; +// using System.Text; +// using System.Threading; +// using System.Threading.Tasks; +// using NetDaemon.Common; -namespace NetDaemon.Daemon.Tests.Daemon -{ - public class HttpClientFactoryMock : Mock - { - private HttpClient? _httpClient; - private MockHttpMessageHandler? _handler; - public MockHttpMessageHandler? MessageHandler => _handler; +// namespace NetDaemon.Daemon.Tests.Daemon +// { +// public class HttpClientFactoryMock : Mock +// { +// private HttpClient? _httpClient; +// private MockHttpMessageHandler? _handler; +// public MockHttpMessageHandler? MessageHandler => _handler; - public HttpClientFactoryMock() - { - } +// public HttpClientFactoryMock() +// { +// } - public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) - { - _handler = new MockHttpMessageHandler(response, statusCode); - _httpClient = new HttpClient(_handler); - Setup(x => x.CreateClient(It.IsAny())).Returns(_httpClient!); - } - } +// public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) +// { +// _handler = new MockHttpMessageHandler(response, statusCode); +// _httpClient = new HttpClient(_handler); +// Setup(x => x.CreateClient(It.IsAny())).Returns(_httpClient!); +// } +// } - public class HttpHandlerMock : Mock - { - private HttpClient? _httpClient; - private MockHttpMessageHandler? _handler; +// public class HttpHandlerMock : Mock +// { +// private HttpClient? _httpClient; +// private MockHttpMessageHandler? _handler; - public MockHttpMessageHandler? MessageHandler => _handler; +// public MockHttpMessageHandler? MessageHandler => _handler; - public HttpHandlerMock() - { - } +// public HttpHandlerMock() +// { +// } - public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) - { - _handler = new MockHttpMessageHandler(response, statusCode); - _httpClient = new HttpClient(_handler); - Setup(x => x.CreateHttpClient(It.IsAny())).Returns(_httpClient!); - } - } +// public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) +// { +// _handler = new MockHttpMessageHandler(response, statusCode); +// _httpClient = new HttpClient(_handler); +// Setup(x => x.CreateHttpClient(It.IsAny())).Returns(_httpClient!); +// } +// } - public class MockHttpMessageHandler : HttpMessageHandler - { - private readonly string _response; - private readonly HttpStatusCode _StatusCode; +// public class MockHttpMessageHandler : HttpMessageHandler +// { +// private readonly string _response; +// private readonly HttpStatusCode _StatusCode; - private string? _requestContent; +// private string? _requestContent; - public string? RequestContent => _requestContent; +// public string? RequestContent => _requestContent; - public MockHttpMessageHandler(string response, HttpStatusCode statusCode = HttpStatusCode.OK) - { - _response = response; - _StatusCode = statusCode; - } +// public MockHttpMessageHandler(string response, HttpStatusCode statusCode = HttpStatusCode.OK) +// { +// _response = response; +// _StatusCode = statusCode; +// } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var responseMessage = new HttpResponseMessage(_StatusCode); +// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +// { +// var responseMessage = new HttpResponseMessage(_StatusCode); - if (request is not null && request.Content is not null) - _requestContent = await request.Content.ReadAsStringAsync().ConfigureAwait(false); +// if (request is not null && request.Content is not null) +// _requestContent = await request.Content.ReadAsStringAsync().ConfigureAwait(false); - responseMessage.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(_response)); - return responseMessage; - } - } -} \ No newline at end of file +// responseMessage.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(_response)); +// return responseMessage; +// } +// } +// } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs index 064af692b..7f3be7b0e 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; +using NetDaemon.Daemon.Fakes; using Xunit; namespace NetDaemon.Daemon.Tests.Daemon diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs index dab9cfe35..4222f1b8c 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using NetDaemon.Common; using Xunit; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.Daemon { @@ -14,7 +15,7 @@ public class HostTestApp : NetDaemon.Common.NetDaemonApp { } - public class NetDaemonTests : DaemonHostTestBase + public class NetDaemonTests : CoreDaemonHostTestBase { public NetDaemonTests() : base() { @@ -24,8 +25,8 @@ public NetDaemonTests() : base() public async Task EventShouldCallCorrectFunction() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); dynamic helloWorldDataObject = GetDynamicDataObject(HelloWorldData); @@ -42,7 +43,7 @@ public async Task EventShouldCallCorrectFunction() return Task.CompletedTask; }); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(isCalled); @@ -53,15 +54,16 @@ public async Task EventShouldCallCorrectFunction() public async Task AttributeServiceCallShouldFindCorrectFunction() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); var app = new AssmeblyDaemonApp(); app.Id = "id"; DefaultDaemonHost.InternalRunningAppInstances[app.Id] = app; - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); // ACT await app.HandleAttributeInitialization(DefaultDaemonHost).ConfigureAwait(false); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.Single(app.DaemonCallBacksForServiceCalls); @@ -85,17 +87,16 @@ public async Task GetStateReturnsCorrectEntityState() // ARRANGE // Fake what is coming from hass client - var task = RunDefauldDaemonUntilCanceled(); + await FakeDaemonInit().ConfigureAwait(false); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); // ACT var entity = DefaultDaemonHost.GetState("light.correct_entity"); - await task; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.NotNull(entity); - Assert.Equal(entity?.State, "on"); + Assert.Equal("on", entity?.State); } // Todo: Add tests to test objects and arrays from the dynamic conversion @@ -104,6 +105,7 @@ public async Task GetStateReturnsCorrectEntityState() public async Task OtherEventShouldNotCallCorrectFunction() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); dynamic dataObject = GetDynamicDataObject(); DefaultHassClientMock.AddCustomEvent("CUSTOM_EVENT", dataObject); @@ -117,26 +119,12 @@ public async Task OtherEventShouldNotCallCorrectFunction() return Task.CompletedTask; }); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(isCalled); } - [Fact] - public async Task RunConnectedReturnsARunningTask() - { - // ARRANGE - - // ACT - var (runTask, _) = ReturnRunningDefauldDaemonHostTask(); - await Task.Delay(10); - - // ASSERT - Assert.False(runTask.IsCompleted || runTask.IsCanceled); - await runTask; - } - [Fact] public async Task RunNotConnectedCompletesTask() { @@ -164,76 +152,76 @@ public void RunNullReferenceToHassClientShouldThrowException() public async Task SendEventShouldCallCorrectMethod() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); var eventData = GetDynamicDataObject(); - var task = RunDefauldDaemonUntilCanceled(); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); // ACT await DefaultDaemonHost.SendEvent("test_event", eventData); - await task; + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT var expandoObject = (ExpandoObject)eventData; - DefaultHassClientMock.Verify(n => n.SendEvent("test_event", expandoObject)); + VerifyEventSent("test_event", expandoObject); } [Fact] public async Task SendEventWithNullDataShouldCallCorrectMethod() { // ARRANGE - var task = RunDefauldDaemonUntilCanceled(); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); + await FakeDaemonInit().ConfigureAwait(false); // ACT await DefaultDaemonHost.SendEvent("test_event"); - await task; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.Verify(n => n.SendEvent("test_event", null)); + VerifyEventSent("test_event"); } [Fact] public async Task SpeakShouldCallCorrectService() { + await FakeDaemonInit().ConfigureAwait(false); // ARRANGE - DefaultHassClientMock.FakeStates["media_player.fakeplayer"] = new HassState + SetEntityState(new() { - EntityId = "media_player.fakeplayer", - }; + EntityId = "media_player.fakeplayer" + }); DefaultDaemonHost.InternalDelayTimeForTts = 0; // For testing // ACT - var (daemonTask, _) = ReturnRunningDefauldDaemonHostTask(); - - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); - DefaultDaemonHost.Speak("media_player.fakeplayer", "Hello test!"); var (_, expObject) = GetDynamicObject( ("entity_id", "media_player.fakeplayer"), ("message", "Hello test!") ); - - await Task.Delay(50); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.Verify(n => n.CallService("tts", "google_cloud_say", expObject, true)); - - await WaitUntilCanceled(daemonTask); + VerifyCallService("tts", "google_cloud_say", expObject, true); } [Fact] public async Task SpeakShouldWaitUntilMediaPlays() { // ARRANGE + SetEntityState(new() + { + EntityId = "media_player.fakeplayer", + Attributes = new() + { + ["entity_id"] = "media_player.fakeplayer", + ["message"] = "Hello test!", + ["media_duration"] = 0.2 + } + } + ); // Get a running default Daemon - var (daemonTask, _) = ReturnRunningDefauldDaemonHostTask(700); - - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); - - DefaultDaemonHost.InternalDelayTimeForTts = 0; // Allow now extra waittime + await FakeDaemonInit(500).ConfigureAwait(false); // Expected data call service var (expectedAttruibutes, expectedAttributesExpObject) = GetDynamicObject( @@ -241,18 +229,6 @@ public async Task SpeakShouldWaitUntilMediaPlays() ("message", "Hello test!") ); - // Add the player that we want to fake having with the fake playing 0.1 second media duration - dynamic currentStateAttributes = new ExpandoObject(); - currentStateAttributes.media_duration = 0.1; - - DefaultDaemonHost.InternalState["media_player.fakeplayer"] = new EntityState - { - EntityId = "media_player.fakeplayer", - Attribute = currentStateAttributes - }; - - // await Task.Delay(100); - // ACT DefaultDaemonHost.Speak("media_player.fakeplayer", "Hello test!"); DefaultDaemonHost.Speak("media_player.fakeplayer", "Hello test!"); @@ -261,13 +237,12 @@ public async Task SpeakShouldWaitUntilMediaPlays() await Task.Delay(50); - DefaultHassClientMock.Verify(n => n.CallService("tts", "google_cloud_say", expectedAttributesExpObject, true), Times.Once); + VerifyCallService("tts", "google_cloud_say", expectedAttributesExpObject, true, Times.Once()); - await Task.Delay(150); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // Called twice - DefaultHassClientMock.Verify(n => n.CallService("tts", "google_cloud_say", expectedAttributesExpObject, true), Times.Exactly(2)); + VerifyCallService("tts", "google_cloud_say", expectedAttributesExpObject, true, Times.Exactly(2)); - await WaitUntilCanceled(daemonTask); } [Fact] @@ -282,6 +257,7 @@ public async Task StopCallsCloseClient() public async Task SubscribeChangedStateForEntityWillMakeCorrectCallback() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); string? reportedState = ""; @@ -294,7 +270,7 @@ public async Task SubscribeChangedStateForEntityWillMakeCorrectCallback() return Task.CompletedTask; }); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.Equal("on", reportedState); @@ -304,6 +280,7 @@ public async Task SubscribeChangedStateForEntityWillMakeCorrectCallback() public async Task SubscribeChangedStateForAllChangesWillMakeCorrectCallbacks() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); DefaultHassClientMock.AddChangedEvent("light.mylight", fromState: "on", toState: "off"); @@ -317,7 +294,7 @@ public async Task SubscribeChangedStateForAllChangesWillMakeCorrectCallbacks() return Task.CompletedTask; }); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout(); // ASSERT Assert.Equal(2, nrOfTimesCalled); @@ -327,6 +304,7 @@ public async Task SubscribeChangedStateForAllChangesWillMakeCorrectCallbacks() public async Task ChangedEventHaveNullDataShouldThrowException() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); DefaultHassClientMock.FakeEvents.Enqueue(new HassEvent { EventType = "state_changed", @@ -334,7 +312,7 @@ public async Task ChangedEventHaveNullDataShouldThrowException() }); // ACT - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); //ASSERT LoggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); @@ -344,6 +322,7 @@ public async Task ChangedEventHaveNullDataShouldThrowException() public async Task CancelChangedStateForSubscriptionWillNotMakeCallback() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); bool isCalled = false; // ACT @@ -358,7 +337,7 @@ public async Task CancelChangedStateForSubscriptionWillNotMakeCallback() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(isCalled); @@ -369,8 +348,8 @@ public async Task CancelChangedStateForSubscriptionWillNotMakeCallback() public async Task CallServiceEventShouldCallCorrectFunction() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); var dynObject = GetDynamicDataObject(HelloWorldData); - var daemonTask = await GetConnectedNetDaemonTask(); var isCalled = false; @@ -386,7 +365,7 @@ public async Task CallServiceEventShouldCallCorrectFunction() DefaultHassClientMock.AddCallServiceEvent("custom_domain", "any_service", dynObject); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(isCalled); @@ -397,6 +376,7 @@ public async Task CallServiceEventShouldCallCorrectFunction() public async Task CallServiceEventOtherShouldNotCallFunction() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); var dynObject = GetDynamicDataObject(HelloWorldData); DefaultHassClientMock.AddCallServiceEvent("custom_domain", "other_service", dynObject); @@ -411,7 +391,7 @@ public async Task CallServiceEventOtherShouldNotCallFunction() return Task.CompletedTask; }); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.False(isCalled); Assert.True(string.IsNullOrEmpty(message)); @@ -445,12 +425,12 @@ public async Task DelayStateChangeShouldReturnTrue() // ARRANGE // ACT + await FakeDaemonInit().ConfigureAwait(false); using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, to: "on"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); - await RunDefauldDaemonUntilCanceled(); - + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(delayResult.Task.Result); Assert.Empty(DefaultDaemonApp.InternalStateActions); @@ -460,13 +440,13 @@ public async Task DelayStateChangeShouldReturnTrue() public async Task DelayStateChangeWithToAndFromShouldReturnTrue() { // ARRANGE - + await FakeDaemonInit().ConfigureAwait(false); // ACT using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, to: "on", from: "off"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(delayResult.Task.Result); @@ -476,14 +456,15 @@ public async Task DelayStateChangeWithToAndFromShouldReturnTrue() [Fact] public async Task DelayStateChangeWithToAndFromWrongShouldNotComplete() { - // ARRANGE + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); // ACT using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, to: "on", from: "unknown"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(delayResult.Task.IsCompleted); @@ -494,13 +475,13 @@ public async Task DelayStateChangeWithToAndFromWrongShouldNotComplete() public async Task DelayStateLambdaChangeShouldReturnTrue() { // ARRANGE - + await FakeDaemonInit().ConfigureAwait(false); // ACT using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, o) => n?.State == "on"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(delayResult.Task.Result); @@ -511,13 +492,13 @@ public async Task DelayStateLambdaChangeShouldReturnTrue() public async Task DelayStateLambdaChangeShouldNotComplete() { // ARRANGE - + await FakeDaemonInit().ConfigureAwait(false); // ACT using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, o) => n?.State == "on"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "on", toState: "off"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(delayResult.Task.IsCompleted); @@ -528,13 +509,14 @@ public async Task DelayStateLambdaChangeShouldNotComplete() public async Task DelayStateChangeCancelShouldReturnFalse() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); // ACT using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, to: "on"); delayResult.Cancel(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(delayResult.Task.Result); @@ -614,12 +596,13 @@ public async Task StateChangeHasAreaInformation() EntityId = "binary_sensor.pir", DeviceId = "device_id" }; + await FakeDaemonInit().ConfigureAwait(false); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); // ACT - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.Equal("Correct name", DefaultDaemonHost.InternalState["binary_sensor.pir"].Area); @@ -629,11 +612,10 @@ public async Task StateChangeHasAreaInformation() public async Task SetStateShouldKeepSameArea() { // ARRANGE - var task = RunDefauldDaemonUntilCanceled(); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); + await FakeDaemonInit().ConfigureAwait(false); // ACT var state = await DefaultDaemonHost.SetStateAsync("light.ligth_in_area", "on", ("attr", "value")); - await task; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); /// ASSERT Assert.Equal("Area", state?.Area); } diff --git a/tests/NetDaemon.Daemon.Tests/DaemonHostTestBase.cs b/tests/NetDaemon.Daemon.Tests/DaemonHostTestBase.cs deleted file mode 100644 index a6040fcd3..000000000 --- a/tests/NetDaemon.Daemon.Tests/DaemonHostTestBase.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Dynamic; -using System.Threading; -using System.Threading.Tasks; -using Moq; -using NetDaemon.Common.Reactive; -using NetDaemon.Daemon.Storage; -using NetDaemon.Daemon.Tests.Daemon; -using Xunit; - -namespace NetDaemon.Daemon.Tests -{ - public class BaseTestApp : Common.NetDaemonApp { } - - public class BaseTestRxApp : NetDaemonRxApp { } - - public partial class DaemonHostTestBase : IAsyncLifetime - { - private readonly Common.NetDaemonApp _defaultDaemonApp; - private readonly NetDaemonHost _defaultDaemonHost; - private readonly BaseTestRxApp _defaultDaemonRxApp; - private readonly Mock _defaultDataRepositoryMock; - private readonly HassClientMock _defaultHassClientMock; - private readonly HttpHandlerMock _defaultHttpHandlerMock; - private readonly Mock _defaultMockedRxApp; - private readonly LoggerMock _loggerMock; - private readonly NetDaemonHost _notConnectedDaemonHost; - - internal DaemonHostTestBase() - { - _loggerMock = new LoggerMock(); - _defaultHassClientMock = HassClientMock.DefaultMock; - _defaultDataRepositoryMock = new Mock(); - - _defaultHttpHandlerMock = new HttpHandlerMock(); - _defaultDaemonHost = new NetDaemonHost( - _defaultHassClientMock.Object, - _defaultDataRepositoryMock.Object, - _loggerMock.LoggerFactory, - _defaultHttpHandlerMock.Object); - - _defaultDaemonApp = new BaseTestApp(); - _defaultDaemonApp.Id = "app_id"; - _defaultDaemonApp.IsEnabled = true; - - _defaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; - - _defaultDaemonRxApp = new BaseTestRxApp(); - _defaultDaemonRxApp.Id = "app_rx_id"; - _defaultDaemonRxApp.IsEnabled = true; - _defaultDaemonHost.InternalRunningAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; - - _defaultMockedRxApp = new Mock() { CallBase = true }; - _defaultMockedRxApp.Object.Id = "app_rx_mock_id"; - _defaultMockedRxApp.Object.IsEnabled = true; - _defaultMockedRxApp.Setup(n => n.CreateObservableIntervall(It.IsAny(), It.IsAny())).Returns(new Mock().Object); - _defaultDaemonHost.InternalRunningAppInstances[_defaultMockedRxApp.Object.Id!] = _defaultMockedRxApp.Object; - - _notConnectedDaemonHost = new NetDaemonHost(HassClientMock.MockConnectFalse.Object, _defaultDataRepositoryMock.Object, _loggerMock.LoggerFactory); - } - - public Common.NetDaemonApp DefaultDaemonApp => _defaultDaemonApp; - - public NetDaemonHost DefaultDaemonHost => _defaultDaemonHost; - public BaseTestRxApp DefaultDaemonRxApp => _defaultDaemonRxApp; - public Mock DefaultDataRepositoryMock => _defaultDataRepositoryMock; - public HassClientMock DefaultHassClientMock => _defaultHassClientMock; - public HttpHandlerMock DefaultHttpHandlerMock => _defaultHttpHandlerMock; - public Mock DefaultMockedRxApp => _defaultMockedRxApp; - public string HelloWorldData => "Hello world!"; - - public LoggerMock LoggerMock => _loggerMock; - - public NetDaemonHost NotConnectedDaemonHost => _notConnectedDaemonHost; - - async Task IAsyncLifetime.DisposeAsync() - { - await _defaultDaemonApp.DisposeAsync().ConfigureAwait(false); - await _defaultDaemonRxApp.DisposeAsync().ConfigureAwait(false); - await _defaultDaemonHost.DisposeAsync().ConfigureAwait(false); - await _defaultMockedRxApp.Object.DisposeAsync().ConfigureAwait(false); - } - - public dynamic GetDynamicDataObject(string testData = "testdata") - { - var expandoObject = new ExpandoObject(); - dynamic dynamicData = expandoObject; - dynamicData.Test = testData; - return dynamicData; - } - - public (dynamic, ExpandoObject) GetDynamicObject(params (string, object)[] dynamicParameters) - { - var expandoObject = new ExpandoObject(); - var dict = expandoObject as IDictionary; - - foreach (var (name, value) in dynamicParameters) - { - dict[name] = value; - } - return (expandoObject, expandoObject); - } - - public async Task InitializeAsync() - { - await _defaultDaemonApp.StartUpAsync(_defaultDaemonHost).ConfigureAwait(false); - await _defaultDaemonRxApp.StartUpAsync(_defaultDaemonHost).ConfigureAwait(false); - await _defaultMockedRxApp.Object.StartUpAsync(_defaultDaemonHost).ConfigureAwait(false); - } - - public (Task, CancellationTokenSource) ReturnRunningDefauldDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) - { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - return (_defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token), cancelSource); - } - - public (Task, CancellationTokenSource) ReturnRunningNotConnectedDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) - { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - return (_notConnectedDaemonHost.Run("host", 8123, false, "token", cancelSource.Token), cancelSource); - } - - public async Task RunDefauldDaemonUntilCanceled(short milliSeconds = 200, bool overrideDebugNotCancel = false) - { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - try - { - await _defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Expected behaviour - } - } - - public async Task WaitUntilCanceled(Task task) - { - try - { - await task.ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Expected behaviour - } - } - - protected async Task GetConnectedNetDaemonTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) - { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - - var daemonTask = _defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, cancelSource.Token); - return daemonTask; - } - - protected async Task WaitForDefaultDaemonToConnect(NetDaemonHost daemonHost, CancellationToken stoppingToken) - { - var nrOfTimesCheckForConnectedState = 0; - - while (!daemonHost.Connected && !stoppingToken.IsCancellationRequested) - { - await Task.Delay(50, stoppingToken).ConfigureAwait(false); - if (nrOfTimesCheckForConnectedState++ > 5) - break; - } - } - } -} \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs index 439f3eece..25c2cef4b 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs @@ -23,6 +23,7 @@ using System.Collections.Generic; using System.Linq; using NetDaemon.Common.Configuration; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.DaemonRunner.Api { diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs index 497eeefe2..30744d4ec 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs @@ -12,6 +12,7 @@ using NetDaemon.Daemon.Config; using NetDaemon.Service.App; using Xunit; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.DaemonRunner.App { diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs new file mode 100644 index 000000000..d96d5c21f --- /dev/null +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System; +using System.Reactive.Linq; +using NetDaemon.Common.Reactive; + +namespace NetDaemon.Daemon.Tests.Reactive +{ + + /// cool multiple lines + public class FakeApp : NetDaemonRxApp + { + public override void Initialize() + { + Entity("binary_sensor.kitchen") + .StateChanges + .Where(e => e.New?.State == "on" && e.Old?.State == "off") + .Subscribe(s => + { + Entity("light.kitchen").TurnOn(); + }); + + Entity("sensor.temperature") + .StateAllChanges + .Where(e => e.New?.Attribute?.battery_level < 15) + .Subscribe(s => + { + this.CallService("notify", "notify", new { title = "Hello from Home Assistant" }); + }); + } + } +} diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs new file mode 100644 index 000000000..30abb5763 --- /dev/null +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs @@ -0,0 +1,504 @@ +using System; +using System.Dynamic; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using JoySoftware.HomeAssistant.Client; +using Moq; +using NetDaemon.Common.Fluent; +using NetDaemon.Common.Reactive; +using NetDaemon.Daemon.Fakes; +using Xunit; + +namespace NetDaemon.Daemon.Tests.Reactive +{ + /// + /// Tests the fluent API parts of the daemon + /// + /// + /// Mainly the tests checks if correct underlying call to "CallService" + /// has been made. + /// + public class FakeTests : CoreDaemonHostTestBase + { + public FakeTests() : base() + { + } + + [Fact] + public async Task CallServiceShouldCallCorrectFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var (dynObj, expObj) = GetDynamicObject( + ("attr", "value")); + + // ACT + DefaultDaemonRxApp.CallService("mydomain", "myservice", dynObj); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + VerifyCallServiceTuple("mydomain", "myservice", ("attr", "value")); + } + + [Fact] + public async Task NewAllEventDataShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.StateAllChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task NewEventShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.EventChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddCustomEvent("AN_EVENT", new { somedata = "hello" }); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task NewEventMissingDataAttributeShouldReturnNull() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + string? missingAttribute = "has initial value"; + + // ACT + DefaultDaemonRxApp.EventChanges + .Subscribe(s => + { + missingAttribute = s.Data?.missing_data; + }); + + var expandoObj = new ExpandoObject(); + dynamic dynExpObject = expandoObj; + dynExpObject.a_parameter = "hello"; + + DefaultHassClientMock.AddCustomEvent("AN_EVENT", dynExpObject); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.Null(missingAttribute); + } + + [Fact] + public async Task NewStateEventShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task RunScriptShouldCallCorrectFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + + var (dynObj, expObj) = GetDynamicObject( + ("attr", "value")); + + // ACT + DefaultDaemonRxApp.RunScript("myscript"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + + + DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); + } + + [Fact] + public async Task RunScriptWithDomainShouldCallCorrectFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var (dynObj, expObj) = GetDynamicObject( + ("attr", "value")); + + // ACT + DefaultDaemonRxApp.RunScript("script.myscript"); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); + } + + [Fact] + public async Task SameStateEventShouldNotCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.False(called); + } + [Fact] + public async Task SetStateShouldReturnCorrectData() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + + var (dynObj, expObj) = GetDynamicObject( + ("attr", "value")); + + // ACT + DefaultDaemonRxApp.SetState("sensor.any_sensor", "on", dynObj); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj)); + } + + [Fact] + public async Task StartupAsyncShouldThrowIfDaemonIsNull() + { + INetDaemonHost? host = null; + + // ARRANGE ACT ASSERT + await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)); + } + + [Fact] + public async Task StateShouldReturnCorrectEntity() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + + // ACT + var entity = DefaultDaemonRxApp.State("binary_sensor.pir"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.NotNull(entity); + } + + [Fact] + public async Task StateShouldReturnNullIfAttributeNotExist() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + + // ACT + var entity = DefaultDaemonRxApp.State("binary_sensor.pir"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.Null(entity?.Attribute?.not_exists); + } + + [Fact] + public async Task StatesShouldReturnCorrectEntity() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + // ACT + var entity = DefaultDaemonRxApp.States.FirstOrDefault(n => n.EntityId == "binary_sensor.pir"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT + Assert.NotNull(entity); + Assert.Equal("binary_sensor.pir", entity?.EntityId); + } + + [Fact] + public async Task EntityIdsShouldReturnCorrectItems() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + // ACT + var entities = DefaultDaemonRxApp.EntityIds.ToList(); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT + Assert.NotNull(entities); + Assert.Equal(8, entities.Count()); + } + + [Fact] + public async Task UsingEntitiesLambdaNewEventShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) + .StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task CallbackObserverAttributeMissingShouldReturnNull() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + + string? missingString = "has initial value"; + + // ACT + DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) + .StateChanges + .Subscribe(s => + { + missingString = s.New.Attribute?.missing_attribute; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.Null(missingString); + } + + [Fact] + public async Task UsingEntitiesNewEventShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.Entities("binary_sensor.pir", "binary_sensor.pir_2") + .StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task UsingEntityNewEventShouldCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.Entity("binary_sensor.pir") + .StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.True(called); + } + + [Fact] + public async Task UsingEntityNewEventShouldNotCallFunction() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + var called = false; + + // ACT + DefaultDaemonRxApp.Entity("binary_sensor.other_pir") + .StateChanges + .Subscribe(s => + { + called = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.False(called); + } + [Fact] + public async Task WhenStateStaysSameForTimeItShouldCallFunction() + { + await FakeDaemonInit().ConfigureAwait(false); + + bool isRun = false; + using var ctx = DefaultDaemonRxApp.StateChanges + .Where(t => t.New.EntityId == "binary_sensor.pir") + .NDSameStateFor(TimeSpan.FromMilliseconds(50)) + .Subscribe(e => + { + isRun = true; + }); + + DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + Assert.True(isRun); + } + + [Fact] + public async Task SavedDataShouldReturnSameDataUsingExpando() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + dynamic data = new ExpandoObject(); + data.Item = "Some data"; + + // ACT + DefaultDaemonRxApp.SaveData("data_exists", data); + var collectedData = DefaultDaemonRxApp.GetData("data_exists"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // ASSERT + Assert.Equal(data, collectedData); + } + + [Fact] + public async Task GetDataShouldReturnCachedValue() + { + // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + // ACT + + DefaultDaemonRxApp.SaveData("GetDataShouldReturnCachedValue_id", "saved data"); + + DefaultDaemonRxApp.GetData("GetDataShouldReturnCachedValue_id"); + + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT + DefaultDataRepositoryMock.Verify(n => n.Get(It.IsAny()), Times.Never); + DefaultDataRepositoryMock.Verify(n => n.Save(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task TestFakeAppTurnOnCorrectLight() + { + // Add the app to test + await AddAppInstance(new FakeApp()); + + // Init NetDaemon core runtime + await FakeDaemonInit().ConfigureAwait(false); + + // Fake a changed event from en entity + AddChangedEvent("binary_sensor.kitchen", "off", "on"); + + // Run the NetDemon Core to process the messages + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // Verify that netdaemon called light.turn_on + VerifyCallService("light", "turn_on", "light.kitchen"); + } + + + [Fact] + public async Task TestFakeAppCallNoteWhenBatteryLevelBelowValue() + { + // Add the app to test + await AddAppInstance(new FakeApp()); + + // Init NetDaemon core runtime + await FakeDaemonInit().ConfigureAwait(false); + + // Fake a changed event from en entity + AddChangeEvent(new() + { + EntityId = "sensor.temperature", + State = 10.0, + Attributes = new() + { + ["battery_level"] = 18.2 + } + } + , new() + { + EntityId = "sensor.temperature", + State = 10.0, + Attributes = new() + { + ["battery_level"] = 12.0 + } + }); + + // Run the NetDemon Core to process the messages + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + + // Verify that netdaemon called light.turn_on + VerifyCallService("notify", "notify", new { title = "Hello from Home Assistant" }); + } + } +} \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs index 22e282cc3..e187702bf 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Moq; using NetDaemon.Common; +using NetDaemon.Daemon.Fakes; using Xunit; namespace NetDaemon.Daemon.Tests.Fluent @@ -14,7 +15,7 @@ namespace NetDaemon.Daemon.Tests.Fluent /// Mainly the tests checks if correct underlying call to "CallService" /// has been made. /// - public class FluentCameraTests : DaemonHostTestBase + public class FluentCameraTests : CoreDaemonHostTestBase { public FluentCameraTests() : base() { @@ -35,7 +36,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] @@ -53,7 +54,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] @@ -77,7 +78,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] @@ -95,7 +96,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] @@ -112,8 +113,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, + VerifyCallServiceTimes(service_call, Times.Once()); + VerifyCallServiceTuple("camera", service_call, ("media_player", "media_player.player"), ("format", "anyformat"), ("entity_id", entityId) @@ -134,8 +135,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, + VerifyCallServiceTimes(service_call, Times.Once()); + VerifyCallServiceTuple("camera", service_call, ("filename", "filename"), ("duration", 1), ("lookback", 2), @@ -157,8 +158,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, + VerifyCallServiceTimes(service_call, Times.Once()); + VerifyCallServiceTuple("camera", service_call, ("filename", "filename"), ("entity_id", entityId) ); @@ -179,7 +180,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] @@ -197,7 +198,7 @@ await DefaultDaemonApp // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); - DefaultHassClientMock.VerifyCallService("camera", service_call, ("entity_id", entityId)); + DefaultHassClientMock.VerifyCallServiceTuple("camera", service_call, ("entity_id", entityId)); } [Fact] diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs index 41e521b12..7d18d1879 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs @@ -2,6 +2,7 @@ using System.Dynamic; using System.Threading.Tasks; using Moq; +using NetDaemon.Daemon.Fakes; using NetDaemon.Daemon.Storage; using Xunit; diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs index 9bf1a27bc..a4f02e0e7 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs @@ -5,6 +5,8 @@ using Microsoft.Extensions.Logging; using Moq; using NetDaemon.Common; +using NetDaemon.Common.Fluent; +using NetDaemon.Daemon.Fakes; using Xunit; namespace NetDaemon.Daemon.Tests.Fluent @@ -16,7 +18,7 @@ namespace NetDaemon.Daemon.Tests.Fluent /// Mainly the tests checks if correct underlying call to "CallService" /// has been made. /// - public class FluentTests : DaemonHostTestBase + public class FluentTests : CoreDaemonHostTestBase { public FluentTests() : base() { @@ -30,9 +32,7 @@ public async Task EntityOnStateChangedForTimeTurnOffLightCallsCorrectServiceCall var lastChanged = new DateTime(2020, 1, 1, 1, 1, 1, 20); var lastUpdated = new DateTime(2020, 1, 1, 1, 1, 1, 50); - var daemonTask = RunDefauldDaemonUntilCanceled(300); //overrideDebugNotCancel: true - - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); + await FakeDaemonInit(300).ConfigureAwait(false); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -42,27 +42,23 @@ public async Task EntityOnStateChangedForTimeTurnOffLightCallsCorrectServiceCall .TurnOff() .Execute(); - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "off", + AddChangedEvent("binary_sensor.pir", "on", "off", lastUpdated, lastChanged); // ASSERT await Task.Delay(10); // After 10ms we should not have call - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Never()); + VerifyCallServiceTimes("turn_off", Times.Never()); await Task.Delay(300); // After 30ms we should have call - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Once()); - await daemonTask; - - + VerifyCallServiceTimes("turn_off", Times.Once()); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); } [Fact] public async Task EntityOnStateChangedLamdaTurnOnLightCallsCorrectServiceCall() { // ARRANGE - - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -71,19 +67,19 @@ public async Task EntityOnStateChangedLamdaTurnOnLightCallsCorrectServiceCall() .TurnOn() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] public async Task EntityOnStateChangedLamdaWithMultipleEntitiesCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); var MotionEnabled = true; DefaultDaemonApp @@ -93,19 +89,18 @@ public async Task EntityOnStateChangedLamdaWithMultipleEntitiesCallsCorrectServi .Toggle() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("toggle", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "toggle", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("toggle", Times.Once()); + VerifyCallServiceTuple("light", "toggle", ("entity_id", "light.correct_entity")); } [Fact] public async Task OneEntityWithSeveralShouldCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -114,20 +109,20 @@ public async Task OneEntityWithSeveralShouldCallsCorrectServiceCall() .Toggle() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("toggle", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "toggle", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("toggle", Times.Once()); + VerifyCallServiceTuple("light", "toggle", ("entity_id", "light.correct_entity")); } [Fact] public async Task EntityOnStateChangedMultipleTimesCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + AddChangedEvent("binary_sensor.pir", "off", "on"); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -136,10 +131,9 @@ public async Task EntityOnStateChangedMultipleTimesCallsCorrectServiceCall() .TurnOn() .Execute(); - await RunDefauldDaemonUntilCanceled(); - - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Exactly(2)); - DefaultHassClientMock.VerifyCallService("light", "turn_on", + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + VerifyCallServiceTimes("turn_on", Times.Exactly(2)); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } @@ -147,9 +141,8 @@ public async Task EntityOnStateChangedMultipleTimesCallsCorrectServiceCall() public async Task EntityOnStateChangedTurnOnLightCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -158,19 +151,18 @@ public async Task EntityOnStateChangedTurnOnLightCallsCorrectServiceCall() .TurnOn() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] public async Task EntityOnStateChangedEntitiesTurnOnLightCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -179,19 +171,20 @@ public async Task EntityOnStateChangedEntitiesTurnOnLightCallsCorrectServiceCall .TurnOn() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] public async Task EntityOnStateChangedEntitiesLambdaTurnOnLightCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); + + AddChangedEvent("binary_sensor.pir", "off", "on"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); //new CancellationTokenSource(1000); // Fake the DefaultDaemonHost.InternalState["light.correct_entity"] = new EntityState @@ -206,19 +199,19 @@ public async Task EntityOnStateChangedEntitiesLambdaTurnOnLightCallsCorrectServi .TurnOn() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] public async Task EntityOnStateChangedTurnOnLightCallsCorrectServiceCallButNoTurnOff() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -234,21 +227,20 @@ public async Task EntityOnStateChangedTurnOnLightCallsCorrectServiceCallButNoTur .TurnOff() .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Never()); + VerifyCallServiceTimes("turn_off", Times.Never()); } [Fact] public async Task EntityOnStateChangedTurnOnLightWithAttributesCallsCorrectServiceCall() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); DefaultDaemonApp .Entity("binary_sensor.pir") @@ -258,10 +250,10 @@ public async Task EntityOnStateChangedTurnOnLightWithAttributesCallsCorrectServi .WithAttribute("transition", 0) .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("transition", 0), ("entity_id", "light.correct_entity")); } @@ -270,9 +262,9 @@ public async Task EntityOnStateChangedTurnOnLightWithAttributesCallsCorrectServi public async Task EntityOnStateDefaultTriggerOnAnyStateChange() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); var triggered = false; // ACT @@ -286,8 +278,7 @@ public async Task EntityOnStateDefaultTriggerOnAnyStateChange() }) .Execute(); - await RunDefauldDaemonUntilCanceled(); - + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(triggered); } @@ -296,9 +287,9 @@ public async Task EntityOnStateDefaultTriggerOnAnyStateChange() public async Task EntityOnStateNotTriggerOnSameState() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "off"); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "off"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); var triggered = false; // ACT @@ -312,7 +303,7 @@ public async Task EntityOnStateNotTriggerOnSameState() }) .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.False(triggered); @@ -322,9 +313,9 @@ public async Task EntityOnStateNotTriggerOnSameState() public async Task EntityOnStateIncludeAttributesTriggerOnSameState() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "off"); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "off"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); var triggered = false; // ACT @@ -338,8 +329,7 @@ public async Task EntityOnStateIncludeAttributesTriggerOnSameState() }) .Execute(); - await RunDefauldDaemonUntilCanceled(); - + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.True(triggered); } @@ -356,8 +346,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("toggle", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "toggle", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("toggle", Times.Once()); + VerifyCallServiceTuple("light", "toggle", ("entity_id", "light.correct_entity")); } [Fact] @@ -372,8 +362,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_off", Times.Once()); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity")); } [Fact] @@ -389,7 +379,7 @@ public async Task EntityFuncExceptionLogsError() .ExecuteAsync()); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Never()); + VerifyCallServiceTimes("turn_off", Times.Never()); LoggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); Assert.Equal("Some error", x.Message); } @@ -398,8 +388,7 @@ public async Task EntityFuncExceptionLogsError() public async Task TurnOffEntityLambdaAttributeSelectionCallsCorrectServiceCall() { // ARRANGE - var daemonTask = RunDefauldDaemonUntilCanceled(); //overrideDebugNotCancel: true - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); + await FakeDaemonInit().ConfigureAwait(false); // ACT await DefaultDaemonApp @@ -409,13 +398,12 @@ await DefaultDaemonApp // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Exactly(3)); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity")); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity2")); - DefaultHassClientMock.VerifyCallService("switch", "turn_off", ("entity_id", "switch.correct_entity")); - - await daemonTask; + VerifyCallServiceTimes("turn_off", Times.Exactly(3)); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity")); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity2")); + VerifyCallServiceTuple("switch", "turn_off", ("entity_id", "switch.correct_entity")); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); } [Fact] @@ -430,17 +418,14 @@ await DefaultDaemonApp // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Never()); + VerifyCallServiceTimes("turn_off", Times.Never()); } [Fact] public async Task TurnOffEntityLamdaSelectionCallsCorrectServiceCall() { // ARRANGE - var daemonTask = RunDefauldDaemonUntilCanceled(); //overrideDebugNotCancel: true - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, CancellationToken.None); - - + await FakeDaemonInit().ConfigureAwait(false); // ACT await DefaultDaemonApp .Entities(n => n.EntityId.StartsWith("light.correct_entity")) @@ -449,11 +434,11 @@ await DefaultDaemonApp // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Exactly(2)); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity")); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity2")); + VerifyCallServiceTimes("turn_off", Times.Exactly(2)); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity")); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity2")); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); } [Fact] @@ -468,10 +453,10 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Exactly(2)); + VerifyCallServiceTimes("turn_off", Times.Exactly(2)); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity")); - DefaultHassClientMock.VerifyCallService("light", "turn_off", ("entity_id", "light.correct_entity2")); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity")); + VerifyCallServiceTuple("light", "turn_off", ("entity_id", "light.correct_entity2")); } [Fact] @@ -485,8 +470,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] @@ -501,8 +486,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("brightness", 100), ("entity_id", "light.correct_entity")); } @@ -520,8 +505,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("brightness", 100), ("color_temp", 230), ("entity_id", "light.correct_entity")); @@ -538,8 +523,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifySetStateTimes("light.correct_entity", Times.Once()); - DefaultHassClientMock.VerifySetState("light.correct_entity", "50"); + VerifySetStateTimes("light.correct_entity", Times.Once()); + VerifySetState("light.correct_entity", "50"); } [Fact] @@ -554,17 +539,16 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifySetStateTimes("light.correct_entity", Times.Once()); - DefaultHassClientMock.VerifySetState("light.correct_entity", "50", ("attr1", "str_value")); + VerifySetStateTimes("light.correct_entity", Times.Once()); + VerifySetState("light.correct_entity", "50", ("attr1", "str_value")); } [Fact] public async Task EntityOnStateCallFunction() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); string? actualToState = ""; string? actualFromState = ""; @@ -579,7 +563,7 @@ public async Task EntityOnStateCallFunction() return Task.CompletedTask; }).Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.Equal("on", actualToState); Assert.Equal("off", actualFromState); @@ -589,10 +573,9 @@ public async Task EntityOnStateCallFunction() [Fact] public async Task EntityOnStateTriggerScript() { + await FakeDaemonInit().ConfigureAwait(false); // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "off"); - - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + AddChangedEvent("binary_sensor.pir", "on", "off"); // ACT DefaultDaemonApp @@ -601,14 +584,16 @@ public async Task EntityOnStateTriggerScript() .RunScript("thescript") .Execute(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); - DefaultHassClientMock.Verify(n => n.CallService("script", "thescript", It.IsAny(), false)); + VerifyCallService("script", "thescript", false); + // Verify(n => n.CallService("script", "thescript", It.IsAny(), false)); } [Fact] public async Task SpeakCallsCorrectServiceCall() { + await FakeDaemonInit().ConfigureAwait(false); // ARRANGE DefaultDaemonHost.InternalDelayTimeForTts = 0; // For testing @@ -618,19 +603,18 @@ await DefaultDaemonApp .Speak("a message") .ExecuteAsync(); - var (daemonTask, _) = ReturnRunningDefauldDaemonHostTask(); - await Task.Delay(20); - var expObject = new ExpandoObject(); + var expObject = new FluentExpandoObject(); dynamic expectedAttruibutes = expObject; expectedAttruibutes.entity_id = "media_player.correct_player"; expectedAttruibutes.message = "a message"; // ASSERT - DefaultHassClientMock.Verify(n => n.CallService("tts", "google_cloud_say", expObject, true)); + VerifyCallService("tts", "google_cloud_say", expObject, true); + // Verify(n => n.CallService("tts", "google_cloud_say", expObject, true)); - await WaitUntilCanceled(daemonTask); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); } [Fact] @@ -644,8 +628,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_play", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_play", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_play", Times.Once()); + VerifyCallServiceTuple("media_player", "media_play", ("entity_id", "media_player.player")); } [Fact] @@ -665,8 +649,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_play", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_play", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_play", Times.Once()); + VerifyCallServiceTuple("media_player", "media_play", ("entity_id", "media_player.player")); } [Fact] @@ -682,7 +666,7 @@ public async Task MediaPlayersFuncExceptionLogsError() .ExecuteAsync()); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_off", Times.Never()); + VerifyCallServiceTimes("turn_off", Times.Never()); LoggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); Assert.Equal("Some error", x.Message); } @@ -698,8 +682,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_play", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_play", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_play", Times.Once()); + VerifyCallServiceTuple("media_player", "media_play", ("entity_id", "media_player.player")); } [Fact] @@ -713,8 +697,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_pause", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_pause", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_pause", Times.Once()); + VerifyCallServiceTuple("media_player", "media_pause", ("entity_id", "media_player.player")); } [Fact] @@ -728,8 +712,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_play_pause", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_play_pause", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_play_pause", Times.Once()); + VerifyCallServiceTuple("media_player", "media_play_pause", ("entity_id", "media_player.player")); } [Fact] @@ -743,8 +727,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("media_stop", Times.Once()); - DefaultHassClientMock.VerifyCallService("media_player", "media_stop", ("entity_id", "media_player.player")); + VerifyCallServiceTimes("media_stop", Times.Once()); + VerifyCallServiceTuple("media_player", "media_stop", ("entity_id", "media_player.player")); } [Fact] @@ -758,8 +742,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("select_option", Times.Once()); - DefaultHassClientMock.VerifyCallService("input_select", "select_option", + VerifyCallServiceTimes("select_option", Times.Once()); + VerifyCallServiceTuple("input_select", "select_option", ("entity_id", "input_select.myselect"), ("option", "option1")); } @@ -775,8 +759,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("select_option", Times.Once()); - DefaultHassClientMock.VerifyCallService("input_select", "select_option", + VerifyCallServiceTimes("select_option", Times.Once()); + VerifyCallServiceTuple("input_select", "select_option", ("entity_id", "input_select.myselect"), ("option", "option1")); } @@ -793,8 +777,8 @@ await DefaultDaemonApp .ExecuteAsync(); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("select_option", Times.Once()); - DefaultHassClientMock.VerifyCallService("input_select", "select_option", + VerifyCallServiceTimes("select_option", Times.Once()); + VerifyCallServiceTuple("input_select", "select_option", ("entity_id", "input_select.myselect"), ("option", "option1")); } @@ -803,15 +787,15 @@ await DefaultDaemonApp public async Task EntityDelayUntilStateChangeShouldReturnTrue() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + await FakeDaemonInit().ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); var delayResult = DefaultDaemonApp .Entity("binary_sensor.pir") .DelayUntilStateChange(to: "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.True(delayResult.Task.IsCompletedSuccessfully); Assert.True(delayResult.Task.Result); @@ -821,15 +805,15 @@ public async Task EntityDelayUntilStateChangeShouldReturnTrue() public async Task EntityDelayUntilStateChangeLamdaShouldReturnTrue() { // ARRANGE - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeDaemonInit().ConfigureAwait(false); - var cancelSource = DefaultHassClientMock.GetSourceWithTimeout(); + AddChangedEvent("binary_sensor.pir", "off", "on"); var delayResult = DefaultDaemonApp .Entity("binary_sensor.pir") .DelayUntilStateChange((to, _) => to?.State == "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.True(delayResult.Task.IsCompletedSuccessfully); Assert.True(delayResult.Task.Result); @@ -839,11 +823,10 @@ public async Task EntityDelayUntilStateChangeLamdaShouldReturnTrue() public async Task EntityInAreaOnStateChangedTurnOnLight() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); - var daemonTask = RunDefauldDaemonUntilCanceled(200, overrideDebugNotCancel: true); - - while (DefaultDaemonHost.Connected == false) - await Task.Delay(10).ConfigureAwait(false); + // Fake the state + SetEntityState("light.ligth_in_area", area: "Area"); // ACT DefaultDaemonApp @@ -854,13 +837,13 @@ public async Task EntityInAreaOnStateChangedTurnOnLight() .Execute(); // light.light_in_area is setup so it has area = Area - DefaultHassClientMock.AddChangedEvent("light.ligth_in_area", "off", "on"); + AddChangedEvent("light.ligth_in_area", "off", "on"); - await daemonTask.ConfigureAwait(false); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.correct_entity")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.correct_entity")); } [Fact] @@ -868,10 +851,10 @@ public async Task EntityInAreaOnStateChangedShouldTurnOn() { // ARRANGE - var daemonTask = RunDefauldDaemonUntilCanceled(200, overrideDebugNotCancel: true); + await FakeDaemonInit().ConfigureAwait(false); - while (DefaultDaemonHost.Connected == false) - await Task.Delay(10).ConfigureAwait(false); + // Fake the state + SetEntityState("light.ligth_in_area", area: "Area"); // ACT DefaultDaemonApp @@ -882,42 +865,41 @@ public async Task EntityInAreaOnStateChangedShouldTurnOn() .Execute(); // light.light_in_area is setup so it has area = Area - DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - await daemonTask.ConfigureAwait(false); + AddChangedEvent("binary_sensor.pir", "off", "on"); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Once()); - DefaultHassClientMock.VerifyCallService("light", "turn_on", ("entity_id", "light.ligth_in_area")); + VerifyCallServiceTimes("turn_on", Times.Once()); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.ligth_in_area")); } [Fact] public async Task RunScriptShouldCallCorrectService() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT await DefaultDaemonApp.RunScript("testscript").ExecuteAsync(); await DefaultDaemonApp.RunScript("script.testscript").ExecuteAsync(); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("testscript", Times.Exactly(2)); + VerifyCallServiceTimes("testscript", Times.Exactly(2)); } [Fact] public async Task SendEventShouldCallCorrectService() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT await DefaultDaemonApp.SendEvent("myevent"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT - DefaultHassClientMock.Verify(n => n.SendEvent("myevent", It.IsAny())); + VerifyEventSent("myevent"); } } diff --git a/tests/NetDaemon.Daemon.Tests/HassClientMock.cs b/tests/NetDaemon.Daemon.Tests/HassClientMock.cs deleted file mode 100644 index 94106c69e..000000000 --- a/tests/NetDaemon.Daemon.Tests/HassClientMock.cs +++ /dev/null @@ -1,322 +0,0 @@ -using JoySoftware.HomeAssistant.Client; -using Moq; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using NetDaemon.Common; -using NetDaemon.Common.Fluent; -using Xunit; - -namespace NetDaemon.Daemon.Tests -{ - public class HassClientMock : Mock - { - internal HassAreas Areas = new HassAreas(); - internal HassDevices Devices = new HassDevices(); - internal HassEntities Entities = new HassEntities(); - internal ConcurrentQueue FakeEvents = new(); - internal ConcurrentDictionary FakeStates = new(); - public HassClientMock() - { -#pragma warning disable 8619, 8620 - // Setup common mocks - Setup(x => x.ConnectAsync(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) - .ReturnsAsync(true); - - SetupDefaultStates(); - - SetupGet(x => x.States).Returns(FakeStates); - - Setup(x => x.GetAllStates(It.IsAny())) - .ReturnsAsync(() => { return (IEnumerable)FakeStates.Values; }); - - Setup(x => x.ReadEventAsync()) - .ReturnsAsync(() => { return FakeEvents.TryDequeue(out var ev) ? ev : null; }); - - Setup(x => x.ReadEventAsync(It.IsAny())) - .ReturnsAsync(() => { return FakeEvents.TryDequeue(out var ev) ? ev : null; }); - - Setup(x => x.GetConfig()).ReturnsAsync(new HassConfig { State = "RUNNING" }); - - Setup(x => x.SetState(It.IsAny(), It.IsAny(), It.IsAny())).Returns( - (entityId, state, attributes) => - { - var fluentAttr = (FluentExpandoObject)attributes; - var attrib = new Dictionary(); - foreach (var attr in (IDictionary)fluentAttr) - attrib[attr.Key] = attr.Value; - - return Task.FromResult(new HassState - { - EntityId = entityId, - State = state, - Attributes = attrib - }); - } - ); - - Setup(n => n.GetAreas()).ReturnsAsync(Areas); - Setup(n => n.GetDevices()).ReturnsAsync(Devices); - Setup(n => n.GetEntities()).ReturnsAsync(Entities); - - // Setup one with area - Devices.Add(new HassDevice { Id = "device_idd", AreaId = "area_idd" }); - Areas.Add(new HassArea { Name = "Area", Id = "area_idd" }); - Entities.Add(new HassEntity - { - EntityId = "light.ligth_in_area", - DeviceId = "device_idd" - }); - } - - public static HassClientMock DefaultMock => new HassClientMock(); - - /// - /// Returns a mock that will always return false when connect to Home Assistant - /// - public static HassClientMock MockConnectFalse - { - get - { - var mock = DefaultMock; - mock.Setup(x => x.ConnectAsync(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) - .ReturnsAsync(false); - return mock; - } - } - - public void AddCallServiceEvent(string domain, string service, dynamic data) - { - // Todo: Refactor to smth smarter - FakeEvents.Enqueue(new HassEvent - { - EventType = "call_service", - Data = new HassServiceEventData - { - Domain = domain, - Service = service, - Data = data - } - }); - } - - public void AddChangedEvent(string entityId, object fromState, object toState, DateTime lastUpdated, DateTime lastChanged) - { - // Todo: Refactor to smth smarter - FakeEvents.Enqueue(new HassEvent - { - EventType = "state_changed", - Data = new HassStateChangedEventData - { - EntityId = entityId, - NewState = new HassState - { - State = toState, - Attributes = new Dictionary - { - ["device_class"] = "motion" - }, - LastChanged = lastChanged, - LastUpdated = lastUpdated - }, - OldState = new HassState - { - State = fromState, - Attributes = new Dictionary - { - ["device_class"] = "motion" - } - } - } - }); - } - - public void AddChangedEvent(string entityId, object fromState, object toState) - { - FakeEvents.Enqueue(new HassEvent - { - EventType = "state_changed", - Data = new HassStateChangedEventData - { - EntityId = entityId, - NewState = new HassState - { - EntityId = entityId, - State = toState, - Attributes = new Dictionary - { - ["device_class"] = "motion" - }, - LastUpdated = DateTime.Now, - LastChanged = DateTime.Now - }, - OldState = new HassState - { - EntityId = entityId, - State = fromState, - Attributes = new Dictionary - { - ["device_class"] = "motion" - } - } - }, - }); - } - - public void AddCustomEvent(string eventType, dynamic? data) - { - FakeEvents.Enqueue(new HassEvent - { - EventType = eventType, - Data = data - }); - } - - public void AssertEqual(HassState hassState, EntityState entity) - { - Assert.Equal(hassState.EntityId, entity.EntityId); - Assert.Equal(hassState.State, entity.State); - Assert.Equal(hassState.LastChanged, entity.LastChanged); - Assert.Equal(hassState.LastUpdated, entity.LastUpdated); - - if (hassState.Attributes?.Keys == null || entity.Attribute == null) - return; - - foreach (var attribute in hassState.Attributes!.Keys) - { - var attr = entity.Attribute as IDictionary ?? - throw new NullReferenceException($"{nameof(entity.Attribute)} catn be null"); - - Assert.True(attr.ContainsKey(attribute)); - Assert.Equal(hassState.Attributes[attribute], - attr![attribute]); - } - } - - /// - /// Gets a cancellation source that does not timeout if debugger is attached - /// - /// - /// - public CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) - { - return (Debugger.IsAttached) - ? new CancellationTokenSource() - : new CancellationTokenSource(milliSeconds); - } - - public void VerifyCallService(string domain, string service, - params (string attribute, object value)[] attributesTuples) - { - var attributes = new FluentExpandoObject(); - foreach (var attributesTuple in attributesTuples) - ((IDictionary)attributes)[attributesTuple.attribute] = attributesTuple.value; - - Verify(n => n.CallService(domain, service, attributes, It.IsAny()), Times.AtLeastOnce); - } - - public void VerifyCallServiceTimes(string service, Times times) - { - Verify(n => n.CallService(It.IsAny(), service, It.IsAny(), It.IsAny()), times); - } - - public void VerifySetState(string entity, string state, - params (string attribute, object value)[] attributesTuples) - { - var attributes = new FluentExpandoObject(); - foreach (var attributesTuple in attributesTuples) - ((IDictionary)attributes)[attributesTuple.attribute] = attributesTuple.value; - - Verify(n => n.SetState(entity, state, attributes), Times.AtLeastOnce); - } - - public void VerifySetStateTimes(string entity, Times times) - { - Verify(n => n.SetState(entity, It.IsAny(), It.IsAny()), times); - } - - private void SetupDefaultStates() - { - FakeStates["light.correct_entity"] = new HassState - { - EntityId = "light.correct_entity", - Attributes = new Dictionary - { - ["test"] = 100 - }, - State = "on" - }; - - FakeStates["light.correct_entity2"] = new HassState - { - EntityId = "light.correct_entity2", - Attributes = new Dictionary - { - ["test"] = 101 - } - }; - - FakeStates["switch.correct_entity"] = new HassState - { - EntityId = "switch.correct_entity", - Attributes = new Dictionary - { - ["test"] = 105 - } - }; - - FakeStates["light.filtered_entity"] = new HassState - { - EntityId = "light.filtered_entity", - Attributes = new Dictionary - { - ["test"] = 90 - } - }; - FakeStates["binary_sensor.pir"] = new HassState - { - EntityId = "binary_sensor.pir", - State = "off", - Attributes = new Dictionary - { - ["device_class"] = "motion" - } - }; - - FakeStates["binary_sensor.pir_2"] = new HassState - { - EntityId = "binary_sensor.pir_2", - State = "off", - Attributes = new Dictionary - { - ["device_class"] = "motion" - } - }; - - FakeStates["media_player.player"] = new HassState - { - EntityId = "media_player.player", - State = "off", - Attributes = new Dictionary - { - ["anyattribute"] = "some attribute" - } - }; - - FakeStates["light.ligth_in_area"] = new HassState - { - EntityId = "light.ligth_in_area", - State = "off", - Attributes = new Dictionary - { - ["anyattribute"] = "some attribute" - } - }; - } - } -} \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/LoggerMock.cs b/tests/NetDaemon.Daemon.Tests/LoggerMock.cs deleted file mode 100644 index 879f3ead2..000000000 --- a/tests/NetDaemon.Daemon.Tests/LoggerMock.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.Extensions.Logging; -using Moq; -using System; - -namespace NetDaemon.Daemon.Tests -{ - public class LoggerMock - { - public LoggerMock() - { - // Setup the mock - MockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(MockLogger.Object); - } - - public ILoggerFactory LoggerFactory => MockLoggerFactory.Object; - public Mock MockLoggerFactory { get; } = new(); - - public ILogger Logger => MockLogger.Object; - public Mock MockLogger { get; } = new(); - - /// - /// Assert if the log has been used at times - /// - /// The loglevel being checked - /// The Times it has been logged - public void AssertLogged(LogLevel level, Times times) - { - MockLogger.Verify( - x => x.Log( - level, - It.IsAny(), - It.Is((v, t) => true), - It.IsAny(), - It.Is>((v, t) => true)), times); - } - - /// - /// Assert if the log has been used at times - /// - /// The loglevel being checked - /// The Times it has been logged - public void AssertLogged(LogLevel level, string message, Times times) - { - MockLogger.Verify( - x => x.Log( - level, - It.IsAny(), - It.Is((v, t) => v.ToString() == message), - It.IsAny(), - It.Is>((v, t) => true)), times); - } - - /// - /// Assert if the log has been used at times - /// - /// The loglevel being checked - /// The Times it has been logged - public void AssertLogged(LogLevel level, Exception exception, string message, Times times) - { - MockLogger.Verify( - x => x.Log( - level, - It.IsAny(), - It.Is((v, t) => v.ToString() == message), - exception, - It.Is>((v, t) => true)), times); - } - } -} \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj b/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj index ab40b00bb..3c3e6a919 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj +++ b/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj @@ -31,6 +31,7 @@ + diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs index 0fff33647..441621249 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs @@ -1,6 +1,7 @@ using JoySoftware.HomeAssistant.Client; using Microsoft.Extensions.Logging; using Moq; +using NetDaemon.Daemon.Fakes; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -28,6 +29,7 @@ public FaultyAppTests() : base() public async Task ARunTimeErrorShouldLogError() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); App .Entity("binary_sensor.pir") @@ -41,7 +43,7 @@ public async Task ARunTimeErrorShouldLogError() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); } @@ -50,6 +52,7 @@ public async Task ARunTimeErrorShouldLogError() public async Task ARunTimeErrorShouldNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); bool eventRun = false; App .Entity("binary_sensor.pir") @@ -73,7 +76,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); @@ -84,6 +87,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() public async Task MissingAttributeShouldNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); bool eventRun = false; App .Entities(e => e.Attribute!.does_not_exist == "yay") @@ -107,7 +111,7 @@ public async Task MissingAttributeShouldNotBreakOtherApps() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); @@ -118,6 +122,7 @@ public async Task MissingAttributeShouldNotBreakOtherApps() public async Task MissingEntityShouldNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); bool eventRun = false; App .Entity("binary_sensor.pir") @@ -141,7 +146,7 @@ public async Task MissingEntityShouldNotBreakOtherApps() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.True(eventRun); diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs index 0e2634857..deebf37fa 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Xunit; using System.Reactive.Linq; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.NetDaemonApp { @@ -29,6 +30,7 @@ public FaultyRxAppTests() : base() public async Task ARunTimeErrorShouldLogError() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); App .Entity("binary_sensor.pir") @@ -40,7 +42,7 @@ public async Task ARunTimeErrorShouldLogError() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); } @@ -49,6 +51,7 @@ public async Task ARunTimeErrorShouldLogError() public async Task MissingEntityShouldNotLogError() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); App .Entity("binary_sensor.pir") @@ -60,7 +63,7 @@ public async Task MissingEntityShouldNotLogError() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); } @@ -69,6 +72,8 @@ public async Task MissingEntityShouldNotLogError() public async Task MissingEntityShouldNotLogErrorAndNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + bool eventRun = false, event2Run = false; App @@ -97,7 +102,7 @@ public async Task MissingEntityShouldNotLogErrorAndNotBreakOtherApps() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.True(eventRun); @@ -109,6 +114,8 @@ public async Task MissingEntityShouldNotLogErrorAndNotBreakOtherApps() public async Task ARunTimeErrorShouldNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + bool eventRun = false, event2Run = false; App @@ -137,7 +144,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); @@ -149,6 +156,8 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + bool eventRun = false, event2Run = false; App @@ -178,7 +187,7 @@ public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() AddDefaultEvent(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); @@ -191,6 +200,8 @@ public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() public async Task ToUnavailableShouldNotBreak() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + bool eventRun = false, event2Run = false; App @@ -220,7 +231,7 @@ public async Task ToUnavailableShouldNotBreak() AddEventFakeGoingUnavailable(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.False(eventRun); @@ -232,6 +243,8 @@ public async Task ToUnavailableShouldNotBreak() public async Task FromUnavailableShouldNotBreak() { // ARRANGE + await FakeDaemonInit().ConfigureAwait(false); + bool eventRun = false, event2Run = false; App @@ -261,7 +274,7 @@ public async Task FromUnavailableShouldNotBreak() AddEventFakeFromUnavailable(); - await RunDefauldDaemonUntilCanceled(); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.False(eventRun); diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs index 0745e44b2..a25def45c 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs @@ -6,6 +6,7 @@ using NetDaemon.Common; using NetDaemon.Common.Fluent; using Xunit; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests.NetDaemonApp { diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs index 0adcc1957..5813de977 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs @@ -16,7 +16,7 @@ namespace NetDaemon.Daemon.Tests.Reactive /// Mainly the tests checks if correct underlying call to "CallService" /// has been made. /// - public class RxAppTest : DaemonHostTestBase + public class RxAppTest : CoreDaemonHostTestBase { public RxAppTest() : base() { @@ -26,23 +26,24 @@ public RxAppTest() : base() public async Task CallServiceShouldCallCorrectFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value")); // ACT DefaultDaemonRxApp.CallService("mydomain", "myservice", dynObj); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT - DefaultHassClientMock.VerifyCallService("mydomain", "myservice", ("attr", "value")); + VerifyCallServiceTuple("mydomain", "myservice", ("attr", "value")); } [Fact] public async Task NewAllEventDataShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -54,7 +55,8 @@ public async Task NewAllEventDataShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -64,7 +66,7 @@ public async Task NewAllEventDataShouldCallFunction() public async Task NewEventShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -76,7 +78,8 @@ public async Task NewEventShouldCallFunction() DefaultHassClientMock.AddCustomEvent("AN_EVENT", new { somedata = "hello" }); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -86,7 +89,8 @@ public async Task NewEventShouldCallFunction() public async Task NewEventMissingDataAttributeShouldReturnNull() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(200); + await FakeDaemonInit().ConfigureAwait(false); + string? missingAttribute = "has initial value"; // ACT @@ -102,7 +106,8 @@ public async Task NewEventMissingDataAttributeShouldReturnNull() DefaultHassClientMock.AddCustomEvent("AN_EVENT", dynExpObject); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.Null(missingAttribute); @@ -112,7 +117,7 @@ public async Task NewEventMissingDataAttributeShouldReturnNull() public async Task NewStateEventShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -124,7 +129,8 @@ public async Task NewStateEventShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -134,13 +140,14 @@ public async Task NewStateEventShouldCallFunction() public async Task RunScriptShouldCallCorrectFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value")); // ACT DefaultDaemonRxApp.RunScript("myscript"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT @@ -152,13 +159,14 @@ public async Task RunScriptShouldCallCorrectFunction() public async Task RunScriptWithDomainShouldCallCorrectFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value")); // ACT DefaultDaemonRxApp.RunScript("script.myscript"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); @@ -168,7 +176,7 @@ public async Task RunScriptWithDomainShouldCallCorrectFunction() public async Task SameStateEventShouldNotCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -180,7 +188,8 @@ public async Task SameStateEventShouldNotCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.False(called); @@ -189,13 +198,14 @@ public async Task SameStateEventShouldNotCallFunction() public async Task SetStateShouldReturnCorrectData() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value")); // ACT DefaultDaemonRxApp.SetState("sensor.any_sensor", "on", dynObj); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj)); @@ -214,12 +224,13 @@ public async Task StartupAsyncShouldThrowIfDaemonIsNull() public async Task StateShouldReturnCorrectEntity() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT var entity = DefaultDaemonRxApp.State("binary_sensor.pir"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.NotNull(entity); @@ -229,12 +240,13 @@ public async Task StateShouldReturnCorrectEntity() public async Task StateShouldReturnNullIfAttributeNotExist() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT var entity = DefaultDaemonRxApp.State("binary_sensor.pir"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.Null(entity?.Attribute?.not_exists); @@ -244,11 +256,11 @@ public async Task StateShouldReturnNullIfAttributeNotExist() public async Task StatesShouldReturnCorrectEntity() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT var entity = DefaultDaemonRxApp.States.FirstOrDefault(n => n.EntityId == "binary_sensor.pir"); - await daemonTask.ConfigureAwait(false); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.NotNull(entity); Assert.Equal("binary_sensor.pir", entity?.EntityId); @@ -258,11 +270,11 @@ public async Task StatesShouldReturnCorrectEntity() public async Task EntityIdsShouldReturnCorrectItems() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT var entities = DefaultDaemonRxApp.EntityIds.ToList(); - await daemonTask.ConfigureAwait(false); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); // ASSERT Assert.NotNull(entities); Assert.Equal(8, entities.Count()); @@ -272,7 +284,7 @@ public async Task EntityIdsShouldReturnCorrectItems() public async Task UsingEntitiesLambdaNewEventShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -285,7 +297,8 @@ public async Task UsingEntitiesLambdaNewEventShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -295,7 +308,7 @@ public async Task UsingEntitiesLambdaNewEventShouldCallFunction() public async Task CallbackObserverAttributeMissingShouldReturnNull() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); string? missingString = "has initial value"; // ACT @@ -308,7 +321,8 @@ public async Task CallbackObserverAttributeMissingShouldReturnNull() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.Null(missingString); @@ -318,7 +332,7 @@ public async Task CallbackObserverAttributeMissingShouldReturnNull() public async Task UsingEntitiesNewEventShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -331,7 +345,8 @@ public async Task UsingEntitiesNewEventShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -341,7 +356,7 @@ public async Task UsingEntitiesNewEventShouldCallFunction() public async Task UsingEntityNewEventShouldCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -354,7 +369,8 @@ public async Task UsingEntityNewEventShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.True(called); @@ -364,7 +380,7 @@ public async Task UsingEntityNewEventShouldCallFunction() public async Task UsingEntityNewEventShouldNotCallFunction() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); var called = false; // ACT @@ -377,7 +393,8 @@ public async Task UsingEntityNewEventShouldNotCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.False(called); @@ -385,7 +402,7 @@ public async Task UsingEntityNewEventShouldNotCallFunction() [Fact] public async Task WhenStateStaysSameForTimeItShouldCallFunction() { - var daemonTask = await GetConnectedNetDaemonTask(300); + await FakeDaemonInit(300).ConfigureAwait(false); bool isRun = false; using var ctx = DefaultDaemonRxApp.StateChanges @@ -398,7 +415,8 @@ public async Task WhenStateStaysSameForTimeItShouldCallFunction() DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + Assert.True(isRun); } @@ -407,7 +425,7 @@ public async Task WhenStateStaysSameForTimeItShouldCallFunction() public async Task SavedDataShouldReturnSameDataUsingExpando() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); dynamic data = new ExpandoObject(); data.Item = "Some data"; @@ -415,7 +433,8 @@ public async Task SavedDataShouldReturnSameDataUsingExpando() DefaultDaemonRxApp.SaveData("data_exists", data); var collectedData = DefaultDaemonRxApp.GetData("data_exists"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT Assert.Equal(data, collectedData); @@ -425,14 +444,15 @@ public async Task SavedDataShouldReturnSameDataUsingExpando() public async Task GetDataShouldReturnCachedValue() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(); + await FakeDaemonInit().ConfigureAwait(false); // ACT DefaultDaemonRxApp.SaveData("GetDataShouldReturnCachedValue_id", "saved data"); DefaultDaemonRxApp.GetData("GetDataShouldReturnCachedValue_id"); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); + // ASSERT DefaultDataRepositoryMock.Verify(n => n.Get(It.IsAny()), Times.Never); DefaultDataRepositoryMock.Verify(n => n.Save(It.IsAny(), It.IsAny()), Times.Once); diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs index 4fb1dee8f..11f90ad88 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; +using NetDaemon.Daemon.Fakes; using Xunit; namespace NetDaemon.Daemon.Tests.Reactive @@ -13,7 +14,7 @@ namespace NetDaemon.Daemon.Tests.Reactive /// Mainly the tests checks if correct underlying call to "CallService" /// has been made. /// - public class RxSchedulerTest : DaemonHostTestBase + public class RxSchedulerTest : CoreDaemonHostTestBase { public RxSchedulerTest() : base() { @@ -155,7 +156,7 @@ public void RunEveryHourShouldThrowExceptionOnErrorFormat() // ACT // ASSERT Assert.Throws(() => - DefaultMockedRxApp.Object.RunEveryHour("no good input", () => System.Console.WriteLine("Test"))); + DefaultMockedRxApp.Object.RunEveryHour("no good input", () => System.Console.WriteLine("Test"))); } [Fact] @@ -194,13 +195,13 @@ public void RunEveryShouldCallCreateObservableIntervall() public async Task RunInFailureShouldLogError() { // ARRANGE - var daemonTask = await GetConnectedNetDaemonTask(160); + await FakeDaemonInit().ConfigureAwait(false); // ACT DefaultDaemonRxApp.RunIn(TimeSpan.FromMilliseconds(100), () => throw new Exception("RxError")); // ASSERT await Task.Delay(150); - await daemonTask; + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); } @@ -209,7 +210,8 @@ public async Task RunInShouldCallFunction() { // ARRANGE var called = false; - var daemonTask = await GetConnectedNetDaemonTask(160); + await FakeDaemonInit().ConfigureAwait(false); + // ACT DefaultDaemonRxApp.RunIn(TimeSpan.FromMilliseconds(100), () => called = true); @@ -218,7 +220,7 @@ public async Task RunInShouldCallFunction() await Task.Delay(150); - await daemonTask.ConfigureAwait(false); + await FakeRunDaemonCoreUntilTimeout().ConfigureAwait(false); Assert.True(called); } } diff --git a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs index dd6cb9ef9..d19caee0a 100644 --- a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs +++ b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using NetDaemon.Common; using Xunit; +using NetDaemon.Daemon.Fakes; namespace NetDaemon.Daemon.Tests { diff --git a/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs new file mode 100644 index 000000000..c5f5a7e32 --- /dev/null +++ b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using JoySoftware.HomeAssistant.Client; +using Moq; +using NetDaemon.Common; +using NetDaemon.Common.Reactive; +using NetDaemon.Daemon.Fakes; +using Xunit; + +namespace NetDaemon.Daemon.Tests +{ + public class BaseTestApp : Common.NetDaemonApp { } + + public class BaseTestRxApp : NetDaemonRxApp { } + + public class CoreDaemonHostTestBase : DaemonHostTestBase, IAsyncLifetime + { + private readonly Common.NetDaemonApp _defaultDaemonApp; + private readonly BaseTestRxApp _defaultDaemonRxApp; + private readonly Mock _defaultMockedRxApp; + + private readonly NetDaemonHost _notConnectedDaemonHost; + + public CoreDaemonHostTestBase() : base() + { + _defaultDaemonApp = new BaseTestApp(); + _defaultDaemonApp.Id = "app_id"; + _defaultDaemonApp.IsEnabled = true; + + DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; + + _defaultDaemonRxApp = new BaseTestRxApp(); + _defaultDaemonRxApp.Id = "app_rx_id"; + _defaultDaemonRxApp.IsEnabled = true; + DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; + + _defaultMockedRxApp = new Mock() { CallBase = true }; + _defaultMockedRxApp.Object.Id = "app_rx_mock_id"; + _defaultMockedRxApp.Object.IsEnabled = true; + _defaultMockedRxApp.Setup(n => n.CreateObservableIntervall(It.IsAny(), It.IsAny())).Returns(new Mock().Object); + DefaultDaemonHost.InternalRunningAppInstances[_defaultMockedRxApp.Object.Id!] = _defaultMockedRxApp.Object; + + _notConnectedDaemonHost = new NetDaemonHost(HassClientMock.MockConnectFalse.Object, DefaultDataRepositoryMock.Object, LoggerMock.LoggerFactory); + + SetupFakeData(); + } + + public void SetupFakeData() + { + SetEntityState( + new HassState + { + EntityId = "light.correct_entity", + Attributes = new Dictionary + { + ["test"] = 100 + }, + State = "on" + }); + SetEntityState( + new HassState + { + EntityId = "light.correct_entity2", + Attributes = new Dictionary + { + ["test"] = 101 + }, + State = "off" + }); + + + SetEntityState(new() + { + EntityId = "switch.correct_entity", + Attributes = new Dictionary + { + ["test"] = 105 + } + }); + + SetEntityState(new() + { + EntityId = "light.filtered_entity", + Attributes = new Dictionary + { + ["test"] = 90 + } + }); + SetEntityState(new() + { + EntityId = "binary_sensor.pir", + State = "off", + Attributes = new Dictionary + { + ["device_class"] = "motion" + } + }); + + SetEntityState(new() + { + EntityId = "binary_sensor.pir_2", + State = "off", + Attributes = new Dictionary + { + ["device_class"] = "motion" + } + }); + + SetEntityState(new() + { + EntityId = "media_player.player", + State = "off", + Attributes = new Dictionary + { + ["anyattribute"] = "some attribute" + } + }); + + SetEntityState( new() + { + EntityId = "light.ligth_in_area", + State = "off", + Attributes = new Dictionary + { + ["anyattribute"] = "some attribute" + } + }); + } + + /// + /// Clean-up any initialized test objects + /// + public new async Task DisposeAsync() + { + await base.DisposeAsync().ConfigureAwait(false); + + await _defaultDaemonApp.DisposeAsync().ConfigureAwait(false); + await _defaultDaemonRxApp.DisposeAsync().ConfigureAwait(false); + await _defaultMockedRxApp.Object.DisposeAsync().ConfigureAwait(false); + } + + /// + /// Initializes on each test run + /// + public new async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(false); + + await _defaultDaemonApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + await _defaultDaemonRxApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + await _defaultMockedRxApp.Object.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + } + + public BaseTestRxApp DefaultDaemonRxApp => _defaultDaemonRxApp; + public Mock DefaultMockedRxApp => _defaultMockedRxApp; + public Common.NetDaemonApp DefaultDaemonApp => _defaultDaemonApp; + public string HelloWorldData => "Hello world!"; + + public (Task, CancellationTokenSource) ReturnRunningNotConnectedDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) + { + var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel + ? new CancellationTokenSource() + : new CancellationTokenSource(milliSeconds); + return (_notConnectedDaemonHost.Run("host", 8123, false, "token", cancelSource.Token), cancelSource); + } + + + } +} \ No newline at end of file