Files
MeterVault/tests/Integration.Tests/ExportRoundTripTests.cs
T
schmidt.florian e223278771 M7: release polish — export/import, docs, easy docker push
- Easy docker push (MQTTower ergonomics + the image push it lacks): VERSION file →
  version-tag.yml (semver-guard auto-tag) → docker-publish.yml (buildx multi-arch → GHCR,
  registry centralized for one-line retarget to git.finalfactory.de) + ci.yml + build-and-push.ps1.
- Verified end to end: deploy/Dockerfile builds; docker compose stack (app + timescaledb) comes
  up healthy; /healthz and the dashboard respond in-container.
- JSON config export/import (ExportService) with id remapping on restore + GET /export, POST
  /import endpoints; round-trip test preserves meter→type, meter-scoped tariff, category links.
- README, HA/Tasmota/MQTT wiring guide (docs/wiring.md), Unraid template.
- CLAUDE.md updated to reflect the built codebase.
- i18n: locale-aware number/currency formatting (de-DE); full de UI string localization deferred.

95 tests green (56 Core + 39 integration).

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 12:25:03 +02:00

77 lines
3.1 KiB
C#

using MeterVault.Core.Domain;
using MeterVault.Infrastructure.Backup;
using MeterVault.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
namespace MeterVault.Integration.Tests;
/// <summary>
/// JSON config export/import (SDD §10): a round-trip through an emptied database preserves the
/// relationships (meter → energy type, meter-scoped tariff, category membership) after id remapping.
/// </summary>
[Collection("Timescale")]
public sealed class ExportRoundTripTests(TimescaleFixture fx)
{
[Fact]
public async Task Export_then_import_preserves_relationships()
{
string json;
await using (var db = fx.CreateContext())
{
await WipeAllAsync(db);
await DatabaseSeeder.SeedAsync(db);
var elec = await db.EnergyTypes.FirstAsync(t => t.Key == "electricity");
var strom = await db.CostCategories.FirstAsync(c => c.Name == "Strom");
var meter = new Meter { Name = "Export Meter", EnergyTypeId = elec.Id, Mode = MeterMode.CumulativeCounter, Unit = "kWh" };
db.Meters.Add(meter);
await db.SaveChangesAsync();
db.Tariffs.Add(new Tariff { ScopeType = TariffScope.Meter, ScopeId = meter.Id, Component = TariffComponent.UnitPrice, Value = 0.30, Unit = "EUR/kWh", ValidFrom = new DateOnly(2024, 1, 1) });
db.CostCategoryMembers.Add(new CostCategoryMember { CategoryId = strom.Id, MeterId = meter.Id });
await db.SaveChangesAsync();
json = await new ExportService(db).ExportJsonAsync();
}
await using (var db = fx.CreateContext())
{
await WipeAllAsync(db);
await new ExportService(db).ImportJsonAsync(json);
}
await using (var db = fx.CreateContext())
{
var meter = await db.Meters.SingleAsync(m => m.Name == "Export Meter");
Assert.True(await db.EnergyTypes.AnyAsync(t => t.Id == meter.EnergyTypeId));
var tariff = await db.Tariffs.SingleAsync(t => t.ScopeType == TariffScope.Meter);
Assert.Equal(meter.Id, tariff.ScopeId);
var member = await db.CostCategoryMembers.SingleAsync();
Assert.Equal(meter.Id, member.MeterId);
Assert.True(await db.CostCategories.AnyAsync(c => c.Id == member.CategoryId));
await WipeAllAsync(db);
}
}
private static async Task WipeAllAsync(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.IngestionEndpoints.ExecuteDeleteAsync();
await db.CostCategories.ExecuteDeleteAsync();
await db.EnergyTypes.ExecuteDeleteAsync();
await db.AppSettings.ExecuteDeleteAsync();
}
}