Vier neue xUnit-Testprojekte (LehrerApp.Tests, .Data.Tests, .Desktop.Tests, .Sync.Tests) mit zusammen 104 Tests: GradingService, SchoolYearService, Repositories gegen In-Memory-LiteDB, Mitarbeits-Aggregation (3.2), Zeugnisnotenberechnung (2.4) und ConflictResolver. Dabei zwei echte Fehler in der Sync-Schicht gefunden und behoben: - SyncEvent.EventId fehlte [BsonId], wodurch EventQueue.Acknowledge() nie etwas aus der Queue löschte. - ConflictResolver verglich Zeitstempel unterschiedlicher DateTimeKind direkt (LiteDB liefert Local statt Utc zurück), was die Gleichstand- Regel außerhalb von UTC+0 verfälschte. SchoolYearService.CurrentSchoolYear()/RecentSchoolYears() um ein optionales today-Argument erweitert, um den Schuljahreswechsel deterministisch zu testen; LiteDbContext um einen Stream-Konstruktor für In-Memory-Tests.
41 lines
1.6 KiB
C#
41 lines
1.6 KiB
C#
using LehrerApp.Sync.Models;
|
|
|
|
namespace LehrerApp.Sync;
|
|
|
|
/// <summary>
|
|
/// Desktop gewinnt gegen Companion.
|
|
/// Bei Gleichstand: späterer Timestamp gewinnt.
|
|
/// </summary>
|
|
public class ConflictResolver(EventQueue queue)
|
|
{
|
|
public ConflictEntry? TryResolve(SyncEvent remote, string localDeviceId)
|
|
{
|
|
var local = queue.GetPending()
|
|
.FirstOrDefault(e => e.EntityType == remote.EntityType
|
|
&& e.EntityId == remote.EntityId
|
|
&& e.DeviceId != remote.DeviceId);
|
|
if (local is null) return null;
|
|
|
|
var winner = (local.DeviceType, remote.DeviceType) switch
|
|
{
|
|
(DeviceType.Desktop, DeviceType.Companion) => local,
|
|
(DeviceType.Companion, DeviceType.Desktop) => remote,
|
|
// ToUniversalTime(): LiteDB liefert DateTime beim Auslesen aus der Queue als Kind=Local
|
|
// zurück (Ticks werden dabei um die lokale Zeitzone verschoben). DateTime-Vergleiche
|
|
// berücksichtigen Kind nicht, sondern vergleichen nur rohe Ticks — ein direkter Vergleich
|
|
// von local.Timestamp (Local, aus der Queue) mit remote.Timestamp (Utc, vom Server) wäre
|
|
// daher außerhalb von UTC+0 falsch.
|
|
_ => local.Timestamp.ToUniversalTime() >= remote.Timestamp.ToUniversalTime() ? local : remote,
|
|
};
|
|
|
|
if (winner == remote) queue.Acknowledge([local.EventId]);
|
|
|
|
return new ConflictEntry
|
|
{
|
|
LocalEvent = local,
|
|
RemoteEvent = remote,
|
|
Resolution = winner == local ? "LocalWon" : "RemoteWon",
|
|
};
|
|
}
|
|
}
|