fix: stop unified-mailbox from mutating client-returned email objects
fetchUnifiedEmails, fanOutUnifiedQuery and the cross-account fanOutCrossQuery stamped accountId/accountLabel/source* directly onto each email object returned by the per-account client. Those objects are shared references; mutating them in place could surprise any caller that retained them (and corrupt an account-state snapshot). Decorate shallow copies instead, at all three fan-out sites. The original fix/unified-mailbox-no-mutation branch predated the cross-account "All accounts" feature and only covered two sites; this re-applies the fix to main's current code, including the third (shared/group) fan-out site, and preserves all five stamped fields. Flips the characterisation test to assert the client's object is left untouched.
This commit is contained in:
committed by
Linus Rath
parent
de56229ef2
commit
70aaf0aac1
@@ -120,16 +120,17 @@ describe('fetchUnifiedEmails', () => {
|
||||
expect(result).toEqual({ emails: [], total: 0, hasMore: false, errors: new Map() });
|
||||
});
|
||||
|
||||
it('CHARACTERISATION: mutates the source email objects in place (shared reference)', async () => {
|
||||
it('does NOT mutate the source email objects (decorates copies)', async () => {
|
||||
const original = makeEmail('m1', '2026-01-01T00:00:00Z');
|
||||
const acc = makeAccount(
|
||||
{ accountId: 'A', accountLabel: 'Label A', mailboxes: [makeMailbox({ role: 'inbox' })] },
|
||||
{ getEmails: vi.fn(async (): Promise<FetchResult> => ({ emails: [original], total: 1, hasMore: false })) },
|
||||
);
|
||||
await fetchUnifiedEmails([acc], 'inbox', 20, 0);
|
||||
// The very object passed back by the client was mutated, not a copy.
|
||||
expect(original.accountId).toBe('A');
|
||||
expect(original.accountLabel).toBe('Label A');
|
||||
const res = await fetchUnifiedEmails([acc], 'inbox', 20, 0);
|
||||
// The returned email carries the account info, but the client's object is untouched.
|
||||
expect(res.emails[0]).toMatchObject({ id: 'm1', accountId: 'A', accountLabel: 'Label A' });
|
||||
expect('accountId' in original).toBe(false);
|
||||
expect('accountLabel' in original).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user