fix(sync): replicate deletions and keep photos off the wire
Reusing the backup snapshot for sync had two bugs that would bite real use: 1. Deletions didn't propagate. exportInventory() filters isDeleted=false (user backups are tombstone-free by design) and the JSON codec dropped isDeleted (omitted on encode, hardcoded false on decode) — so deleting a variety on one device never reached the others. Now the codec round-trips isDeleted (emitted only when true, so backups stay byte-identical and it's backward compatible), and a new exportForSync() includes tombstones. The importer already merges them LWW-correctly. 2. Photos bloated events. The snapshot embeds photo bytes as base64, so a real inventory would blow past relay event-size limits and sync would silently fail. exportForSync() omits attachments entirely (photos stay device-local; media sync is a separate, deferred concern). buildSnapshot() now uses exportForSync(); backups keep using exportInventory (photos in, tombstones out) unchanged. Tests: a deletion replicates as a tombstone; the sync snapshot carries no photo bytes while a sealed backup still does.
This commit is contained in:
parent
225880fc64
commit
21e4357970
4 changed files with 83 additions and 11 deletions
|
|
@ -162,6 +162,40 @@ void main() {
|
|||
expect(await dbB.select(dbB.varieties).get(), hasLength(1));
|
||||
});
|
||||
|
||||
test('sync replicates a DELETION (tombstone), not just additions', () async {
|
||||
final (serviceA, _, _) = newService(dbA);
|
||||
final repoA = newTestRepository(dbA);
|
||||
final id = await repoA.addQuickVariety(label: 'To delete');
|
||||
|
||||
// First sync: device B receives the variety, alive.
|
||||
final (serviceB, _, _) = newService(dbB);
|
||||
await serviceB.applySnapshot(await serviceA.buildSnapshot());
|
||||
var onB = (await dbB.select(dbB.varieties).get()).firstWhere((v) => v.id == id);
|
||||
expect(onB.isDeleted, isFalse);
|
||||
|
||||
// A deletes it; the next sync carries the tombstone.
|
||||
await repoA.softDeleteVariety(id);
|
||||
await serviceB.applySnapshot(await serviceA.buildSnapshot());
|
||||
onB = (await dbB.select(dbB.varieties).get()).firstWhere((v) => v.id == id);
|
||||
expect(onB.isDeleted, isTrue); // deletion replicated, not resurrected
|
||||
});
|
||||
|
||||
test('the sync snapshot carries NO photo bytes (photos stay device-local)',
|
||||
() async {
|
||||
final (serviceA, _, _) = newService(dbA);
|
||||
final repoA = newTestRepository(dbA);
|
||||
final id = await repoA.addQuickVariety(label: 'With photo');
|
||||
await repoA.addPhoto(id, Uint8List.fromList(List.filled(5000, 42)));
|
||||
|
||||
final syncBytes = await serviceA.buildSnapshot();
|
||||
expect(utf8.decode(syncBytes).contains('bytesBase64'), isFalse);
|
||||
expect(syncBytes.length, lessThan(5000)); // the 5 KB photo isn't in it
|
||||
|
||||
// A sealed BACKUP, by contrast, still embeds the photo.
|
||||
final backup = await serviceA.buildSealedBackup();
|
||||
expect(backup.length, greaterThan(5000));
|
||||
});
|
||||
|
||||
test('legacy plain exports still restore (no seal, direct parse)', () async {
|
||||
final repoA = newTestRepository(dbA);
|
||||
await repoA.addQuickVariety(label: 'Old export');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue