mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: never resend a group's members from an outdated copy #34
A group whose latest server version the device could not store waits on the stale list, and nothing uploads for it until it has been fetched again. Membership edits bypassed that check: adding a contact to such a group sent the whole member list as the device last knew it, which removed members another client had added meanwhile. Membership edits for a stale group now wait for that fetch, and the report lists them.
This commit is contained in:
@@ -186,6 +186,36 @@ describe('device sync engine with the real planners', () => {
|
||||
expect(h.server.get('ContactCard', 'a', group)!.members).toEqual({ [uid]: true });
|
||||
});
|
||||
|
||||
it('waits with a membership edit while the group card is stale, instead of resending its members', async () => {
|
||||
const { h, ada, group } = await adaJoinsFriends();
|
||||
const [bob] = addServerCards(h, ['Bob']);
|
||||
const bobUid = h.server.get('ContactCard', 'a', bob)!.uid as string;
|
||||
// Another client adds Bob; this device cannot store the new version of the group this run.
|
||||
h.server.serverUpdate('ContactCard', 'a', group, { members: { [bobUid]: true } });
|
||||
h.deps.planners = {
|
||||
...h.deps.planners,
|
||||
contacts: {
|
||||
...contactsPlanner,
|
||||
planGroupDownload: (card, local, ctx) => {
|
||||
if (card.id === group) throw new Error('cannot store this group now');
|
||||
return contactsPlanner.planGroupDownload(card, local, ctx);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await h.run();
|
||||
|
||||
const adaUid = h.server.get('ContactCard', 'a', ada)!.uid as string;
|
||||
expect(h.server.get('ContactCard', 'a', group)!.members).toEqual({ [bobUid]: true });
|
||||
expect(h.contacts().find((c) => c.name === 'Ada Lovelace')).toMatchObject({ dirty: true });
|
||||
|
||||
h.deps.planners = { contacts: contactsPlanner, calendar: calendarPlanner };
|
||||
expect((await h.run()).outcome).toBe('ok');
|
||||
|
||||
expect(h.server.get('ContactCard', 'a', group)!.members).toEqual({ [bobUid]: true, [adaUid]: true });
|
||||
expect(h.contacts().find((c) => c.name === 'Ada Lovelace')).toMatchObject({ dirty: false });
|
||||
});
|
||||
|
||||
it('uploads a removal from a group in a run that downloads no contact', async () => {
|
||||
const h = real();
|
||||
// A keyed phone row shows the editor works in place, so a missing membership row is a removal.
|
||||
|
||||
@@ -708,6 +708,11 @@ export class ContactsSync extends ItemSync {
|
||||
this.env.report.itemError({ ref: id ? refOf(acct, id) : 'membership', side: 'upload', type: 'unknownGroup' });
|
||||
continue;
|
||||
}
|
||||
if (this.isStale(acct, group.sourceId)) {
|
||||
// Its shadow is behind the server: members built from it could undo server changes. The contact stays dirty.
|
||||
this.env.report.itemError({ ref: group.sourceId as string, side: 'upload', type: 'stale', description: 'Waits until the server version could be stored' });
|
||||
continue;
|
||||
}
|
||||
let pending = byGroup.get(group.sourceId as string);
|
||||
if (!pending) {
|
||||
const held: Held = { kind: this.groupKind, local: group };
|
||||
|
||||
Reference in New Issue
Block a user