using MeterVault.Infrastructure.Costing; using MeterVault.Infrastructure.Import; using MeterVault.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace MeterVault.Integration.Tests; /// /// End-to-end M5 check: load the reference dataset, then confirm the dashboard and admin pages /// render (server prerender) without error and show real data. Cleans up the shared container. /// [Collection("Timescale")] public sealed class DashboardRenderTests(TimescaleFixture fx) { [Fact] public async Task Pages_render_with_reference_data() { using var factory = new MeterVaultAppFactory(fx.ConnectionString); using (var scope = factory.Services.CreateScope()) { var importer = scope.ServiceProvider.GetRequiredService(); await importer.LoadAsync(Path.Combine(AppContext.BaseDirectory, "fixtures")); } try { // The import created the reference meters and their normalized consumption. await using (var db = fx.CreateContext()) { Assert.Contains(await db.Meters.Select(m => m.Name).ToListAsync(), n => n == "Zähler Haus"); Assert.True(await db.Meters.CountAsync() >= 8); Assert.True(await db.Consumption.AnyAsync()); // Regression (audit): Wasser is metered (water tariff), the Kosten Wasser column is // NOT imported, so the category is not double-counted — Dez 2022 = 14 m³ × 5 € = 70 €. var wasser = await db.CostCategories.FirstAsync(c => c.Name == "Wasser"); var rollup = await new CostService(fx).GetCategoryCostsAsync( wasser.Id, new DateTimeOffset(2022, 12, 1, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero)); Assert.Equal(70d, rollup.Sum(r => r.Cost), 1); } using var client = factory.CreateClient(); var overview = await client.GetAsync(new Uri("/", UriKind.Relative)); overview.EnsureSuccessStatusCode(); var html = await overview.Content.ReadAsStringAsync(); Assert.Contains("Overview", html, StringComparison.Ordinal); // These labels live only in the rendered-KPI-card branch, so their presence proves the // summary loaded and the cards rendered (non-ASCII like € is HTML-entity-encoded). Assert.Contains("This month", html, StringComparison.Ordinal); Assert.Contains("This year", html, StringComparison.Ordinal); Assert.Contains("Latest month with data", html, StringComparison.Ordinal); foreach (var path in new[] { "/meters", "/trends", "/import", "/admin/tariffs", "/admin/energy-types" }) { var response = await client.GetAsync(new Uri(path, UriKind.Relative)); response.EnsureSuccessStatusCode(); } } finally { await using var db = fx.CreateContext(); await ClearDataAsync(db); } } private static async Task ClearDataAsync(MeterVaultDbContext db) { await db.Consumption.ExecuteDeleteAsync(); await db.Readings.ExecuteDeleteAsync(); await db.MeterEvents.ExecuteDeleteAsync(); await db.ManualCosts.ExecuteDeleteAsync(); await db.CostCategoryMembers.ExecuteDeleteAsync(); await db.Tariffs.ExecuteDeleteAsync(); await db.Tanks.ExecuteDeleteAsync(); await db.MeterSources.ExecuteDeleteAsync(); await db.Meters.ExecuteDeleteAsync(); await db.ImportBatches.ExecuteDeleteAsync(); } }