using MeterVault.App;
using MeterVault.Core.Domain;
namespace MeterVault.Integration.Tests;
///
/// Hand-entered timestamps and meter-page addresses: the pure pieces behind the reading and event
/// dialogs and the deep links into a meter. No database, so these run without Docker.
///
public sealed class LocalTimeEntryTests
{
private static readonly TimeZoneInfo Berlin = LocalTimeEntry.Resolve("Europe/Berlin");
[Fact]
public void Wall_clock_time_is_read_in_the_instance_timezone()
{
var entry = new LocalTimeEntry(Berlin) { Date = new DateTime(2026, 9, 17), TimeOfDay = new TimeSpan(9, 30, 0) };
Assert.Equal(new DateTimeOffset(2026, 9, 17, 7, 30, 0, TimeSpan.Zero), entry.Utc); // CEST = UTC+2
}
[Fact]
public void A_spring_forward_time_names_no_instant()
{
var entry = new LocalTimeEntry(Berlin) { Date = new DateTime(2026, 3, 29), TimeOfDay = new TimeSpan(2, 30, 0) };
Assert.True(entry.IsSkipped);
Assert.Null(entry.Utc);
}
[Fact]
public void An_ambiguous_autumn_time_resolves_to_standard_time()
{
var entry = new LocalTimeEntry(Berlin) { Date = new DateTime(2026, 10, 25), TimeOfDay = new TimeSpan(2, 30, 0) };
Assert.Equal(new DateTimeOffset(2026, 10, 25, 1, 30, 0, TimeSpan.Zero), entry.Utc); // CET = UTC+1
}
[Fact]
public void Setting_an_instant_round_trips_to_the_minute()
{
var entry = new LocalTimeEntry(Berlin);
entry.Set(new DateTimeOffset(2026, 9, 17, 7, 30, 42, TimeSpan.Zero));
Assert.Equal(new DateTime(2026, 9, 17), entry.Date);
Assert.Equal(new TimeSpan(9, 30, 0), entry.TimeOfDay);
Assert.Equal(new DateTimeOffset(2026, 9, 17, 7, 30, 0, TimeSpan.Zero), entry.Utc);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("Not/AZone")]
public void An_unknown_timezone_falls_back_to_utc(string? id) =>
Assert.Equal(TimeZoneInfo.Utc, LocalTimeEntry.Resolve(id));
[Fact]
public void Meter_links_address_a_tab_and_an_action()
{
Assert.Equal("/meters/7", MeterLinks.Detail(7));
Assert.Equal("/meters/7?tab=events&action=swap", MeterLinks.Event(7, MeterEventType.MeterSwap));
Assert.Equal("/meters/7?tab=readings&action=reading", MeterLinks.QuickEntry(7, MeterMode.CumulativeCounter));
Assert.Equal("/meters/7?tab=events&action=tank-level", MeterLinks.QuickEntry(7, MeterMode.ConsumableBalance));
Assert.Null(MeterLinks.QuickEntry(7, MeterMode.Virtual));
}
[Fact]
public void Source_and_connector_links_carry_the_way_back()
{
Assert.Equal("/meters/7?tab=sources&action=source", MeterLinks.Source(7));
Assert.Equal(
"/meters/7?tab=sources&action=source&source=3&type=Tasmota&connector=12",
MeterLinks.Source(7, sourceId: 3, sourceType: SourceType.Tasmota, connectorId: 12));
Assert.Equal(
"/admin/connectors?new=MqttBroker&meter=7&type=Tasmota",
MeterLinks.NewConnector(7, sourceId: null, SourceType.Tasmota, EndpointType.MqttBroker));
Assert.Equal(
"/admin/connectors?edit=12&meter=7&source=3&type=HomeAssistant",
MeterLinks.EditConnector(7, sourceId: 3, SourceType.HomeAssistant, connectorId: 12));
}
[Fact]
public void Every_event_type_round_trips_through_its_action()
{
foreach (var type in Enum.GetValues())
{
Assert.Equal(type, MeterLinks.EventFor(MeterLinks.ActionFor(type)));
}
Assert.Null(MeterLinks.EventFor(MeterLinks.ActionReading));
Assert.Null(MeterLinks.EventFor(null));
}
[Theory]
[InlineData(null, MeterMode.CumulativeCounter, "analysis", 0)]
[InlineData("readings", MeterMode.CumulativeCounter, "readings", 1)]
[InlineData("EVENTS", MeterMode.CumulativeCounter, "events", 3)]
[InlineData("sources", MeterMode.CumulativeCounter, "sources", 5)]
[InlineData("consumption", MeterMode.CumulativeCounter, "normalized", 2)]
[InlineData("nonsense", MeterMode.CumulativeCounter, "analysis", 0)]
[InlineData("sources", MeterMode.Virtual, "calculation", 3)]
[InlineData("readings", MeterMode.Virtual, "analysis", 0)]
[InlineData("events", MeterMode.Virtual, "events", 1)]
public void Tab_keys_resolve_by_key_and_mode(string? tab, MeterMode mode, string expectedKey, int expectedPanel)
{
// Rewritten on purpose (note ยง10): tabs are addressed by stable keys (D-47), and which panels exist depends on
// the mode, so a key maps to a key first and only then to the position that key has on this meter.
Assert.Equal(expectedKey, MeterLinks.ResolveTab(tab, mode));
Assert.Equal(expectedPanel, MeterLinks.PanelIndex(tab, mode));
}
}