Files
LehrerApp/LehrerApp.Sync.Tests/EventQueueTests.cs
T
adminandClaude Sonnet 5 5bd6967421 fix: Pull-Wasserzeichen sprang über fremde Ereignisse + schärfere Push-Kollisionskontrolle
EventStore.Pull gab bisher den globalen ServerSeq-Höchststand als neuen Cursor zurück statt
den höchsten unter den tatsächlich gelieferten Ereignissen - hatte ein Gerät selbst kurz zuvor
etwas gepusht, sprang sein Pull-Cursor über noch nicht abgeholte Ereignisse anderer Geräte
hinweg und verpasste sie dauerhaft, ohne jeden Fehler.

Ersetzt außerdem die bisherige 30-Sekunden-Heuristik zur Konflikterkennung beim Push durch
exakte BasedOnServerSeq-Prüfung: jedes SyncEvent trägt die ServerSeq, auf der es aufbaut: der
Server lehnt ab, wenn der aktuelle Stand nicht mehr passt. Bei Ablehnung lädt der Client sofort
den neuen Server-Stand nach, löst den Konflikt nach der bestehenden Desktop-vs-Companion/
Timestamp-Politik auf und macht ihn immer in der Konflikt-Review-UI sichtbar, statt die
verworfene Änderung stillschweigend zu verlieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 23:25:04 +02:00

109 lines
3.1 KiB
C#

using LehrerApp.Sync.Models;
using Xunit;
namespace LehrerApp.Sync.Tests;
public sealed class EventQueueTests
{
[Fact]
public void MarkReviewed_EntferntKonfliktAusDerUnreviewedListe()
{
using var temp = new TempEventQueue();
var conflict = new ConflictEntry
{
LocalEvent = MakeEvent(),
RemoteEvent = MakeEvent(),
Resolution = "LocalWon",
};
temp.Queue.AddConflict(conflict);
temp.Queue.MarkReviewed(conflict.Id);
Assert.Empty(temp.Queue.GetUnreviewed());
Assert.Equal(0, temp.Queue.ConflictCount());
}
[Fact]
public void MarkReviewed_UnbekannteId_TutNichtsUndWirftNicht()
{
using var temp = new TempEventQueue();
var exception = Record.Exception(() => temp.Queue.MarkReviewed(Guid.NewGuid()));
Assert.Null(exception);
}
[Fact]
public void GetKnownServerSeq_UnbekannteEntitaet_LiefertNull()
{
using var temp = new TempEventQueue();
Assert.Null(temp.Queue.GetKnownServerSeq("Lesson", Guid.NewGuid().ToString()));
}
[Fact]
public void SetKnownServerSeq_GefolgtVonGet_LiefertDenGesetztenWert()
{
using var temp = new TempEventQueue();
var entityId = Guid.NewGuid().ToString();
temp.Queue.SetKnownServerSeq("Lesson", entityId, 42);
Assert.Equal(42, temp.Queue.GetKnownServerSeq("Lesson", entityId));
}
[Fact]
public void SetKnownServerSeq_ErneutesSetzen_UeberschreibtDenAltenWert()
{
using var temp = new TempEventQueue();
var entityId = Guid.NewGuid().ToString();
temp.Queue.SetKnownServerSeq("Lesson", entityId, 42);
temp.Queue.SetKnownServerSeq("Lesson", entityId, 43);
Assert.Equal(43, temp.Queue.GetKnownServerSeq("Lesson", entityId));
}
[Fact]
public void SetKnownServerSeq_GleicheEntityIdAndererEntityType_BleibtGetrennt()
{
using var temp = new TempEventQueue();
var entityId = Guid.NewGuid().ToString();
temp.Queue.SetKnownServerSeq("Lesson", entityId, 42);
temp.Queue.SetKnownServerSeq("Unit", entityId, 7);
Assert.Equal(42, temp.Queue.GetKnownServerSeq("Lesson", entityId));
Assert.Equal(7, temp.Queue.GetKnownServerSeq("Unit", entityId));
}
private static SyncEvent MakeEvent() => new()
{
DeviceId = "desktop-1",
DeviceType = DeviceType.Desktop,
EntityType = "Student",
EntityId = Guid.NewGuid().ToString(),
Operation = "Save",
Payload = "{}",
};
private sealed class TempEventQueue : IDisposable
{
private readonly string _directory = Path.Combine(
Path.GetTempPath(), $"lehrerapp-sync-tests-eventqueue-{Guid.NewGuid():N}");
public EventQueue Queue { get; }
public TempEventQueue()
{
Directory.CreateDirectory(_directory);
Queue = new EventQueue(Path.Combine(_directory, "queue.db"));
}
public void Dispose()
{
Queue.Dispose();
if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true);
}
}
}