M5: Blazor dashboard (MudBlazor + ApexCharts)
- 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
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user