d5419729e5
- MudBlazor theme (dark default) + responsive drawer/appbar layout + nav. - DashboardService read model: KPIs with period-over-period deltas, category breakdown, "what cost more/less" difference view, monthly trends. - Pages: Overview (KPI cards + DeltaChip + donut + difference table), Trends (range-select bar chart), Meters (list + source status), Import (load reference dataset + CSV dry-run preview), Admin (energy types, tariffs). Charts isolated into components to avoid the ApexCharts/MudBlazor Color/Format name clashes. - ReferenceDataImporter: one-click load of all four sheets as a starter dataset (meters, tank, tariff history, category memberships) — bundled sample CSVs copied to app output. - End-to-end render test: import creates meters + consumption; overview/meters/trends/ import/admin pages all return 200 with KPI cards rendered. 92 tests green (56 Core + 36 integration). Deferred to polish: dedicated PV & oil/consumable panels, meter-detail page, full admin CRUD, prev-year trend overlay. Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
75 lines
3.1 KiB
C#
75 lines
3.1 KiB
C#
using MeterVault.Infrastructure.Import;
|
|
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace MeterVault.Integration.Tests;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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<ReferenceDataImporter>();
|
|
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());
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|