fix: eigener Push ließ Client seinen Pull-Cursor über fremde Ereignisse springen
SyncEngine.PushAsync setzte den lokalen Pull-Cursor bisher aus PushResponse.ServerSequenceNr - dem globalen Zähler über alle Geräte nach dem eigenen Push, nicht dem tatsächlich zugestellten Stand. War beim Server zu diesem Zeitpunkt bereits ein noch nicht abgeholtes Ereignis eines anderen Geräts mit niedrigerer ServerSeq vorhanden, sprang der Cursor darüber hinweg und der direkt folgende Pull bekam 0 Ereignisse, ohne es je angewendet zu haben - ein zweiter, unabhängiger Cursor-Bug mit demselben Symptom wie der vorherige Wasserzeichen-Fix, diesmal client- statt serverseitig. Ergänzt außerdem einen "Vollständigen Sync erzwingen"-Button in den Sync-Einstellungen, damit bereits durch diesen Bug zu weit vorgerückte Geräte ihren Fortschritt manuell zurücksetzen und alle Ereignisse erneut laden können. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,60 @@ public sealed class SyncEngineTests
|
||||
Assert.Equal(2, temp.Queue.GetLastServerSeq());
|
||||
}
|
||||
|
||||
/// Regression: PushAsync setzte den lokalen Pull-Cursor bisher direkt aus PushResponse.
|
||||
/// ServerSequenceNr - dem GLOBALEN Zähler über alle Geräte NACH diesem Push, nicht dem Stand,
|
||||
/// den DIESES Gerät tatsächlich per Pull erhalten hat. Hatte der Server zum Push-Zeitpunkt
|
||||
/// bereits ein noch nicht abgeholtes Ereignis eines ANDEREN Geräts mit niedrigerer ServerSeq,
|
||||
/// sprang der Cursor beim eigenen Push darüber hinweg - der direkt anschließende PullAsync
|
||||
/// fragte dann schon mit einem "since" danach und bekam 0 Ereignisse, obwohl das fremde
|
||||
/// Ereignis nie angewendet wurde. Genau das vom Nutzer beobachtete Symptom ("Client 1 konnte
|
||||
/// übermitteln, Client 2 bekommt weiterhin 'keine Änderungen'"), diesmal über einen anderen
|
||||
/// Pfad als die bereits behobene EventStore.Pull-Wasserzeichen-Berechnung.
|
||||
[Fact]
|
||||
public async Task SyncNowAsync_EigenerPushWaehrendFremdesEreignisNochAussteht_LiefertFremdesEreignisTrotzdem()
|
||||
{
|
||||
using var temp = new TempEventQueue();
|
||||
using var db = NewInMemoryContext();
|
||||
var applier = new EventApplier(db, Key, versions: temp.Queue);
|
||||
var goodStudent = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var foreignEvent = new SyncEvent
|
||||
{
|
||||
DeviceId = "other-device", DeviceType = DeviceType.Desktop,
|
||||
EntityType = nameof(Student), EntityId = goodStudent.Id.ToString(),
|
||||
Operation = "Save", Payload = SyncCrypto.EncryptObject(goodStudent, Key),
|
||||
SequenceNr = 5,
|
||||
};
|
||||
temp.Queue.Enqueue("this-device", DeviceType.Desktop, "Lesson", Guid.NewGuid().ToString(), "Save", "x");
|
||||
var handler = new FakeHttpMessageHandler(req =>
|
||||
{
|
||||
if (req.RequestUri!.AbsolutePath == "/api/sync/push")
|
||||
// Globaler Höchststand (10) schließt das fremde, von DIESEM Gerät noch nicht
|
||||
// abgeholte Ereignis (Seq 5) bereits mit ein - genau das durfte SyncEngine NICHT
|
||||
// als eigenen Pull-Cursor übernehmen.
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{ Content = JsonContent.Create(new PushResponse { ServerSequenceNr = 10 }) };
|
||||
if (req.RequestUri!.AbsolutePath == "/api/sync/pull")
|
||||
{
|
||||
var query = req.RequestUri.Query.TrimStart('?').Split('&')
|
||||
.Select(p => p.Split('=')).ToDictionary(p => p[0], p => p[1]);
|
||||
var since = long.Parse(query["since"]);
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = JsonContent.Create(since < 5
|
||||
? new PullResponse { Events = [foreignEvent], ServerSequenceNr = 5 }
|
||||
: new PullResponse { ServerSequenceNr = since }),
|
||||
};
|
||||
}
|
||||
return new HttpResponseMessage(HttpStatusCode.OK);
|
||||
});
|
||||
var engine = MakeEngine(temp, handler, applier);
|
||||
|
||||
var result = await engine.SyncNowAsync();
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.NotNull(db.Students.FindById(goodStudent.Id));
|
||||
}
|
||||
|
||||
// ── PushAsync: Dedup, BasedOnServerSeq, AssignedServerSeqs (TODO 10.3.4) ────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user