fix: assign uid to contact cards on creation #644

This commit is contained in:
Linus Rath
2026-07-22 19:18:59 +02:00
parent b7c8cd999e
commit 959d4bd6ce
3 changed files with 46 additions and 2 deletions
+2 -2
View File
@@ -400,8 +400,8 @@ export default function ContactsPage() {
}, [clearSelection, toggleContactSelection, groups.length]);
const handleDuplicateContact = useCallback(async (source: ContactCard) => {
const { id: _id, created: _created, updated: _updated, ...rest } = source;
void _id; void _created; void _updated;
const { id: _id, uid: _uid, created: _created, updated: _updated, ...rest } = source;
void _id; void _uid; void _created; void _updated;
const data: Partial<ContactCard> = JSON.parse(JSON.stringify(rest));
if (supportsSync && client) {
await createContact(client, data);
+42
View File
@@ -301,6 +301,48 @@ describe('JMAPClient contact methods', () => {
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
it('should generate a urn:uuid uid when none is provided (#644)', async () => {
const client = createClient();
const fetchSpy = vi.spyOn(globalThis, 'fetch');
mockFetchOnce(fetchSpy, {
methodResponses: [['ContactCard/set', { created: { 'new-contact': { id: 'new-id' } } }, '0']],
});
mockFetchOnce(fetchSpy, {
methodResponses: [['ContactCard/get', { list: [{ ...mockContact, id: 'new-id' }] }, '0']],
});
await client.createContact({
emails: { email: { address: 'trusted@example.com' } },
addressBookIds: { 'ab-1': true },
});
const setBody = JSON.parse(fetchSpy.mock.calls[0][1]?.body as string);
const created = setBody.methodCalls[0][1].create['new-contact'];
expect(created.uid).toMatch(/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
});
it('should preserve a caller-provided uid', async () => {
const client = createClient();
const fetchSpy = vi.spyOn(globalThis, 'fetch');
mockFetchOnce(fetchSpy, {
methodResponses: [['ContactCard/set', { created: { 'new-contact': { id: 'new-id' } } }, '0']],
});
mockFetchOnce(fetchSpy, {
methodResponses: [['ContactCard/get', { list: [{ ...mockContact, id: 'new-id' }] }, '0']],
});
await client.createContact({
uid: 'urn:uuid:12345678-1234-1234-1234-123456789abc',
addressBookIds: { 'ab-1': true },
});
const setBody = JSON.parse(fetchSpy.mock.calls[0][1]?.body as string);
const created = setBody.methodCalls[0][1].create['new-contact'];
expect(created.uid).toBe('urn:uuid:12345678-1234-1234-1234-123456789abc');
});
it('should throw on notCreated error with description', async () => {
const client = createClient();
const fetchSpy = vi.spyOn(globalThis, 'fetch');
+2
View File
@@ -4398,6 +4398,8 @@ export class JMAPClient implements IJMAPClient {
create: {
"new-contact": {
...contactData,
// Stalwart stores the card without one if omitted (#644)
uid: contactData.uid || `urn:uuid:${crypto.randomUUID()}`,
addressBookIds,
}
}