111111
This commit is contained in:
@@ -3,6 +3,7 @@ using Evergreen.Health.Application.Health.Devices.Climate;
|
||||
using Evergreen.Health.Application.Health.Devices.Contracts;
|
||||
using Evergreen.Health.Application.Health.Devices.Curtain;
|
||||
using Evergreen.Health.Application.Health.Devices.Discovery;
|
||||
using Evergreen.Health.Application.Health.Devices.Environment;
|
||||
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
||||
using Evergreen.Health.Application.Health.Models;
|
||||
using Evergreen.Health.Application.Health.Scenes;
|
||||
@@ -16,11 +17,25 @@ namespace Admin.NET.Test.Health;
|
||||
public sealed class ClimateCurtainIntegrationTests
|
||||
{
|
||||
private const string ClimateId = "climate.test_room", CurtainId = "cover.test_curtain";
|
||||
private static HomeAssistantOptions Settings() => new() { Entities = new() { Climate = ClimateId, Curtain = CurtainId } };
|
||||
private const string RoomTemperatureId = "sensor.test_room_temperature";
|
||||
private static HomeAssistantOptions Settings() => new()
|
||||
{ Entities = new() { Climate = ClimateId, Curtain = CurtainId, Temperature = RoomTemperatureId } };
|
||||
private static DeviceActionRunner Runner(Gateway gateway) => new(gateway, TimeProvider.System,
|
||||
new() { VerificationDelay = TimeSpan.Zero, VerificationAttempts = 3 });
|
||||
private static ClimateModule Climate(Gateway gateway) => new(Runner(gateway), Options.Create(Settings()));
|
||||
private static CurtainModule Curtain(Gateway gateway) => new(Runner(gateway), Options.Create(Settings()));
|
||||
private static BedPresenceClimateHeatAction HeatAutomation(Gateway gateway)
|
||||
{
|
||||
var runner = Runner(gateway);
|
||||
var options = Options.Create(Settings());
|
||||
return new(new ClimateModule(runner, options), new IndoorEnvironmentReader(options, TimeProvider.System), runner, options);
|
||||
}
|
||||
|
||||
private static HomeAssistantState RoomTemperature(string celsius = "20", int ageMinutes = 0) => new()
|
||||
{
|
||||
EntityId = RoomTemperatureId, State = celsius, LastReported = DateTimeOffset.UtcNow.AddMinutes(-ageMinutes),
|
||||
Attributes = JsonSerializer.SerializeToElement(new { unit_of_measurement = "°C" })
|
||||
};
|
||||
private static HomeAssistantState ClimateState(string state = "off", int features = 393,
|
||||
double minimum = 5, double maximum = 35, double step = .5) => new()
|
||||
{
|
||||
@@ -43,6 +58,64 @@ public sealed class ClimateCurtainIntegrationTests
|
||||
TargetTemperatureCelsius = target, Reason = "测试室温建议" }
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task Bed_presence_heat_sends_each_command_once_and_confirms_heat_and_22_degrees()
|
||||
{
|
||||
var gateway = new Gateway(ClimateState(), RoomTemperature());
|
||||
var action = HeatAutomation(gateway);
|
||||
Assert.Equal("target-not-configured", await action.ExecuteAsync("climate.other", default));
|
||||
Assert.Equal("readback-confirmed", await action.ExecuteAsync(ClimateId, default));
|
||||
Assert.Collection(gateway.Calls,
|
||||
mode => { Assert.Equal("set_hvac_mode", mode.Service); Assert.Equal("heat", mode.Data!["hvac_mode"]); },
|
||||
temperature => { Assert.Equal("set_temperature", temperature.Service); Assert.Equal(22m, temperature.Data!["temperature"]); });
|
||||
Assert.Equal("heat", gateway.States[ClimateId].State);
|
||||
Assert.Equal(22, gateway.States[ClimateId].GetDoubleAttribute("temperature"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("22", 0, "off", 5, "temperature-not-low")]
|
||||
[InlineData("27", 0, "off", 5, "temperature-not-low")]
|
||||
[InlineData("20", 31, "off", 5, "temperature-stale")]
|
||||
[InlineData("NaN", 0, "off", 5, "temperature-invalid")]
|
||||
[InlineData(null, 0, "off", 5, "temperature-unavailable")]
|
||||
[InlineData("20", 0, "cool", 5, "climate-not-off")]
|
||||
[InlineData("20", 0, "unavailable", 5, "climate-unavailable")]
|
||||
[InlineData("20", 0, "off", 24, "climate-heat-unsupported")]
|
||||
public async Task Bed_presence_heat_checks_room_power_and_target_before_sending(
|
||||
string? roomTemperature, int ageMinutes, string climateState, int minimum, string result)
|
||||
{
|
||||
var gateway = new Gateway(ClimateState(climateState, minimum: minimum));
|
||||
if (roomTemperature is not null) gateway.States[RoomTemperatureId] = RoomTemperature(roomTemperature, ageMinutes);
|
||||
Assert.Equal(result, await HeatAutomation(gateway).ExecuteAsync(ClimateId, default));
|
||||
Assert.Empty(gateway.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bed_presence_heat_leaves_the_temperature_alone_when_heat_mode_cannot_be_confirmed()
|
||||
{
|
||||
var gateway = new Gateway(ClimateState(), RoomTemperature()) { ApplyCommands = false };
|
||||
Assert.Equal("climate-heat-mode-unconfirmed", await HeatAutomation(gateway).ExecuteAsync(ClimateId, default));
|
||||
Assert.Equal("set_hvac_mode", Assert.Single(gateway.Calls).Service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bed_presence_heat_does_not_set_temperature_after_the_user_changes_the_mode()
|
||||
{
|
||||
var gateway = new Gateway(ClimateState(), RoomTemperature());
|
||||
gateway.BeforeRead = read => { if (read == 3) gateway.States[ClimateId] = ClimateState("off"); };
|
||||
Assert.Equal("climate-heat-state-changed", await HeatAutomation(gateway).ExecuteAsync(ClimateId, default));
|
||||
Assert.Equal("set_hvac_mode", Assert.Single(gateway.Calls).Service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bed_presence_heat_requires_both_mode_and_target_in_the_final_readback()
|
||||
{
|
||||
var gateway = new Gateway(ClimateState(), RoomTemperature()) { ModeAfterTemperature = "off" };
|
||||
Assert.Equal("climate-heat-target-unconfirmed", await HeatAutomation(gateway).ExecuteAsync(ClimateId, default));
|
||||
Assert.Equal(22, gateway.States[ClimateId].GetDoubleAttribute("temperature"));
|
||||
Assert.Equal(new[] { "set_hvac_mode", "set_temperature" }, gateway.Calls.Select(command => command.Service));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5)]
|
||||
[InlineData(23.5)]
|
||||
@@ -383,9 +456,12 @@ public sealed class ClimateCurtainIntegrationTests
|
||||
public int Reads { get; private set; }
|
||||
public bool ApplyCommands { get; set; } = true;
|
||||
public bool BecomeUnavailableAfterSend { get; set; }
|
||||
public string? ModeAfterTemperature { get; set; }
|
||||
public Action<int>? BeforeRead { get; set; }
|
||||
public Task<IReadOnlyList<HomeAssistantState>> GetStatesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested(); Reads++;
|
||||
BeforeRead?.Invoke(Reads);
|
||||
return Task.FromResult<IReadOnlyList<HomeAssistantState>>(States.Values.ToArray());
|
||||
}
|
||||
public Task CallServiceAsync(string domain, string service, IReadOnlyDictionary<string, object?> data,
|
||||
@@ -397,7 +473,14 @@ public sealed class ClimateCurtainIntegrationTests
|
||||
if (!ApplyCommands) return Task.CompletedTask;
|
||||
var previous = States[entityId];
|
||||
var attributes = previous.Attributes.EnumerateObject().ToDictionary(item => item.Name, item => (object?)item.Value.Clone());
|
||||
var current = service switch { "set_hvac_mode" => (string)data["hvac_mode"]!, "turn_on" => "heat", "turn_off" => "off", _ => previous.State };
|
||||
var current = service switch
|
||||
{
|
||||
"set_hvac_mode" => (string)data["hvac_mode"]!,
|
||||
"set_temperature" when ModeAfterTemperature is not null => ModeAfterTemperature,
|
||||
"turn_on" => "heat",
|
||||
"turn_off" => "off",
|
||||
_ => previous.State
|
||||
};
|
||||
if (data.TryGetValue("temperature", out var temperature)) attributes["temperature"] = temperature;
|
||||
if (data.TryGetValue("fan_mode", out var fan)) attributes["fan_mode"] = fan;
|
||||
States[entityId] = new() { EntityId = entityId, State = BecomeUnavailableAfterSend ? "unavailable" : current,
|
||||
|
||||
Reference in New Issue
Block a user