fix: Sync-Statusanzeige zeigt nach Neustart fälschlich "nicht konfiguriert"
SyncEngine feuert sein erstes StatusChanged bereits im eigenen Konstruktor. Da SyncEngine und SyncStatusViewModel beide DI-Singletons sind und Letzterer Ersteren erst innerhalb der eigenen Factory aus dem Container holt, lief dieser erste Broadcast ab, bevor SyncStatusViewModel überhaupt abonniert hatte - der Status ging verloren, StatusText blieb bis zum nächsten Auto-Sync oder manuellen Sync beim hartcodierten Default "Kein Server konfiguriert". Fix: Konstruktor ruft nach dem Abonnieren zusätzlich einmal OnStatus(engine.Status) mit dem bereits vorhandenen aktuellen Zustand auf. Regressionstest ergänzt - dabei fehlte LehrerApp.Desktop.Tests das DisableTestParallelization-Attribut (gleicher bekannter LiteDB-BsonMapper.Global-Bug wie in den anderen Testprojekten, sobald zwei Testklassen parallel LiteDbContext konstruieren). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
using Xunit;
|
||||
|
||||
// Gleicher Grund wie in LehrerApp.Data.Tests/AssemblyInfo.cs: LiteDBs geteilter, statischer
|
||||
// BsonMapper.Global verträgt keine parallele Erstzuordnung von Typ-Metadaten über mehrere
|
||||
// Testklassen hinweg (SettingsViewModelTests/SyncStatusViewModelTests konstruieren beide
|
||||
// LiteDbContext).
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -0,0 +1,62 @@
|
||||
using LehrerApp.Data;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Sync;
|
||||
using LehrerApp.Sync.Crypto;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class SyncStatusViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Konstruktor_EngineBereitsVorhanden_ZeigtNichtDenNichtKonfiguriertText()
|
||||
{
|
||||
// SyncEngine feuert sein erstes StatusChanged schon im eigenen Konstruktor - läuft der vor
|
||||
// dem Abonnieren in SyncStatusViewModel (z.B. weil beide DI-Singletons sind und
|
||||
// GetService<SyncEngine>() den Engine-Konstruktor erst innerhalb der VM-Factory auslöst),
|
||||
// ginge dieser erste Status verloren und StatusText bliebe fälschlich beim Default.
|
||||
using var temp = new TempSyncEngine();
|
||||
|
||||
var vm = new SyncStatusViewModel(temp.Engine);
|
||||
|
||||
Assert.True(vm.IsServerConfigured);
|
||||
Assert.NotEqual("Kein Server konfiguriert", vm.StatusText);
|
||||
Assert.Equal("Synchronisiert", vm.StatusText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Konstruktor_KeineEngine_ZeigtNichtKonfiguriertText()
|
||||
{
|
||||
var vm = new SyncStatusViewModel(null);
|
||||
|
||||
Assert.False(vm.IsServerConfigured);
|
||||
Assert.Equal("Kein Server konfiguriert", vm.StatusText);
|
||||
}
|
||||
|
||||
private sealed class TempSyncEngine : IDisposable
|
||||
{
|
||||
private readonly string _queuePath = Path.Combine(
|
||||
Path.GetTempPath(), $"lehrerapp-desktop-tests-{Guid.NewGuid():N}.db");
|
||||
private readonly LiteDbContext _db = new(new MemoryStream());
|
||||
private readonly HttpClient _http = new();
|
||||
public SyncEngine Engine { get; }
|
||||
|
||||
public TempSyncEngine()
|
||||
{
|
||||
var queue = new EventQueue(_queuePath);
|
||||
var key = SyncCrypto.GenerateKey();
|
||||
Engine = new SyncEngine(
|
||||
queue, new ConflictResolver(queue), new EventApplier(_db, key),
|
||||
new AttachmentSyncer(_db, _http, key), _http,
|
||||
new SyncConfig { AutoSyncIntervalMinutes = 5 });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Engine.Dispose(); // disposed auch die EventQueue
|
||||
_db.Dispose();
|
||||
_http.Dispose();
|
||||
if (File.Exists(_queuePath)) File.Delete(_queuePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,16 @@ public partial class SyncStatusViewModel : ObservableObject
|
||||
{
|
||||
_engine = engine;
|
||||
IsServerConfigured = engine is not null;
|
||||
if (_engine is not null) _engine.StatusChanged += OnStatus;
|
||||
if (_engine is not null)
|
||||
{
|
||||
_engine.StatusChanged += OnStatus;
|
||||
// SyncEngine feuert sein erstes StatusChanged bereits im eigenen Konstruktor
|
||||
// (UpdateStatus) - der läuft aber schon, während wir hier noch in GetService<SyncEngine>()
|
||||
// stecken, also VOR dem obigen Abonnieren. Ohne diesen Nachhol-Aufruf bliebe StatusText
|
||||
// nach jedem Neustart beim Default "Kein Server konfiguriert", bis der nächste
|
||||
// Auto-Sync oder Klick auf "Jetzt synchronisieren" den Text erstmals aktualisiert.
|
||||
OnStatus(_engine.Status);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStatus(SyncStatus s)
|
||||
|
||||
@@ -1348,6 +1348,17 @@ die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügba
|
||||
|
||||
**Umsetzung:** War bereits vorhanden (`SyncStatusViewModel`/`SyncStatusBar`), nur hier noch
|
||||
nicht abgehakt.
|
||||
|
||||
**Nachtrag (Bug beim Nutzertest gefunden):** Nach jedem App-Neustart zeigte die Statusleiste
|
||||
fälschlich "Kein Server konfiguriert", obwohl Sync korrekt eingerichtet war — bis zum
|
||||
nächsten Auto-Sync oder Klick auf "Jetzt synchronisieren". Ursache: `SyncEngine` feuert sein
|
||||
erstes `StatusChanged` bereits im eigenen Konstruktor; da `SyncEngine` und
|
||||
`SyncStatusViewModel` beide DI-Singletons sind und Letzterer Ersteren erst innerhalb der
|
||||
eigenen Factory aus dem Container holt, läuft dieser erste Broadcast ab, bevor
|
||||
`SyncStatusViewModel` überhaupt abonniert hat — der Status ging verloren, `StatusText` blieb
|
||||
beim hartcodierten Default. Fix: `SyncStatusViewModel`-Konstruktor ruft nach dem Abonnieren
|
||||
zusätzlich einmal `OnStatus(engine.Status)` mit dem bereits vorhandenen aktuellen Zustand auf
|
||||
(`SyncStatusViewModel.cs`). Regressionstest `SyncStatusViewModelTests.cs`.
|
||||
- [x] **10.1.6** Lokale Schreibvorgänge atomar an die Outbox (`EventQueue`) anbinden — aktuell ist
|
||||
das Sync-Grundgerüst registriert, die Repositories erzeugen aber noch keine Sync-Ereignisse.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user