mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
`contactToWire` sends a key that is present but undefined as null, so a caller passing a card back without `uid`, `id` or `addressBookIds` (the plugin API does) asked the server to clear them. Stalwart 1.0 refuses a null UID and 0.16 silently deletes it; a null `id` or `addressBookIds` fails the update on both. Update mode now leaves those keys out. vCard export prefixed every group member with `urn:uuid:`, but keys are already URIs for groups made in Bulwark (members are keyed by uid) and for groups from CardDAV clients, which gave `MEMBER:urn:uuid:urn:uuid:…`. Keys with a URI scheme are written as they are.
101 lines
4.2 KiB
TypeScript
101 lines
4.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { contactFromWire, contactToWire } from '../jmap/contact-wire';
|
|
import type { ContactCard } from '../jmap/types';
|
|
|
|
describe('contactToWire', () => {
|
|
it('drops client-only fields, which ContactCard/set rejects', () => {
|
|
const wire = contactToWire({
|
|
name: { full: 'A' },
|
|
originalId: 'x', accountId: 'a', accountName: 'n', isShared: true, localAccountId: 'l',
|
|
}, 'create');
|
|
expect(wire).toEqual({ name: { full: 'A' } });
|
|
});
|
|
|
|
it('writes the calendar URIs as RFC 9553 calendars and schedulingAddresses', () => {
|
|
const wire = contactToWire({
|
|
calendarUri: 'https://x.test/cal',
|
|
freeBusyUri: 'https://x.test/fb',
|
|
schedulingUri: 'mailto:a@x.test',
|
|
}, 'create');
|
|
expect(wire).toEqual({
|
|
calendars: {
|
|
cal: { '@type': 'Calendar', kind: 'calendar', uri: 'https://x.test/cal' },
|
|
fb: { '@type': 'Calendar', kind: 'freeBusy', uri: 'https://x.test/fb' },
|
|
},
|
|
schedulingAddresses: { sched: { '@type': 'SchedulingAddress', uri: 'mailto:a@x.test' } },
|
|
});
|
|
});
|
|
|
|
it('sends null for cleared fields on update, but omits them on create', () => {
|
|
const cleared: Partial<ContactCard> = { nicknames: undefined, calendarUri: undefined, schedulingUri: undefined };
|
|
expect(contactToWire(cleared, 'update')).toEqual({ nicknames: null, calendars: null, schedulingAddresses: null });
|
|
expect(contactToWire(cleared, 'create')).toEqual({});
|
|
});
|
|
|
|
it('never clears id, uid or addressBookIds on update', () => {
|
|
const wire = contactToWire({
|
|
id: undefined, uid: undefined, addressBookIds: undefined, nicknames: undefined,
|
|
} as Partial<ContactCard>, 'update');
|
|
expect(wire).toEqual({ nicknames: null });
|
|
|
|
const nulls = contactToWire({ id: null, uid: null, addressBookIds: null } as unknown as Partial<ContactCard>, 'update');
|
|
expect(nulls).toEqual({});
|
|
});
|
|
|
|
it('still sends a uid or addressBookIds that has a value', () => {
|
|
expect(contactToWire({ uid: 'urn:uuid:1', addressBookIds: { ab: true } }, 'update'))
|
|
.toEqual({ uid: 'urn:uuid:1', addressBookIds: { ab: true } });
|
|
});
|
|
|
|
it('leaves calendar links alone when the update does not mention them', () => {
|
|
expect(contactToWire({ keywords: { vip: true } }, 'update')).toEqual({ keywords: { vip: true } });
|
|
});
|
|
|
|
it('leaves media out of an update that does not mention it, which keeps the photos on both versions', () => {
|
|
// An omitted property keeps its value; `media: null` would delete every
|
|
// photo, logo and sound (1.0 contact/set.rs:609-611, same on 0.16).
|
|
const wire = contactToWire({ phones: { p0: { number: '+33612345678' } } }, 'update');
|
|
expect(wire).toEqual({ phones: { p0: { number: '+33612345678' } } });
|
|
expect('media' in wire).toBe(false);
|
|
});
|
|
|
|
it('converts flat vCard addresses to components and SOURCE to a directory entry', () => {
|
|
const wire = contactToWire({
|
|
addresses: { a0: { street: 'Main St 1', locality: 'Town', country: 'DE', fullAddress: 'Main St 1, Town', contexts: { work: true } } },
|
|
source: 'https://x.test/card.vcf',
|
|
}, 'create');
|
|
expect(wire.addresses).toEqual({
|
|
a0: {
|
|
components: [
|
|
{ kind: 'name', value: 'Main St 1' },
|
|
{ kind: 'locality', value: 'Town' },
|
|
{ kind: 'country', value: 'DE' },
|
|
],
|
|
isOrdered: true,
|
|
defaultSeparator: ', ',
|
|
full: 'Main St 1, Town',
|
|
contexts: { work: true },
|
|
},
|
|
});
|
|
expect(wire.directories).toEqual({ source: { '@type': 'Directory', kind: 'entry', uri: 'https://x.test/card.vcf' } });
|
|
});
|
|
});
|
|
|
|
describe('contactFromWire', () => {
|
|
it('fills the flat URI fields from the server card', () => {
|
|
const card = contactFromWire({
|
|
id: 'c1',
|
|
addressBookIds: {},
|
|
calendars: { k: { kind: 'calendar', uri: 'https://x.test/cal' }, f: { kind: 'freeBusy', uri: 'https://x.test/fb' } },
|
|
schedulingAddresses: { s: { uri: 'mailto:a@x.test' } },
|
|
directories: { d: { kind: 'entry', uri: 'https://x.test/card.vcf' } },
|
|
});
|
|
expect(card).toMatchObject({
|
|
calendarUri: 'https://x.test/cal',
|
|
freeBusyUri: 'https://x.test/fb',
|
|
schedulingUri: 'mailto:a@x.test',
|
|
source: 'https://x.test/card.vcf',
|
|
});
|
|
});
|
|
});
|