feat(cardav): implement POST /accounts/:id/sync endpoint

Adds route to sync all enabled addressbooks for an account with mock support for tests.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-05-04 17:54:03 +02:00
parent f895776911
commit 674fe796b3
4 changed files with 145 additions and 6 deletions
+88
View File
@@ -1523,6 +1523,13 @@ describe('CardDAV API Routes', () => {
{ url: 'https://example.com/carddav/addressbook2', displayName: 'Work' }
]
}));
// Mock syncAccount for API route tests
cardavSync._mockSyncAccount(async () => ({
synced: true,
contactsAdded: 5,
contactsUpdated: 3
}));
});
after(async () => {
@@ -1533,6 +1540,9 @@ describe('CardDAV API Routes', () => {
// Reset testConnection mock
const cardavSync = await import('./server/services/cardav-sync.js');
cardavSync._mockTestConnection(null);
// Reset syncAccount mock
cardavSync._mockSyncAccount(null);
});
describe('Account Management', () => {
@@ -1996,4 +2006,82 @@ describe('CardDAV API Routes', () => {
assert.ok(res.data.error.includes('enabled'));
});
});
describe('Sync', () => {
it('POST /accounts/:id/sync - should sync all enabled addressbooks', async () => {
const cardavRouter = await import('./server/routes/cardav.js');
// Create account (which creates addressbooks)
const createReq = {
params: {},
query: {},
body: {
name: 'Sync Test Account',
cardavUrl: 'https://example.com/carddav-sync',
username: 'testuser-sync',
password: 'testpass'
}
};
const createRes = {
statusCode: 200,
status(code) { this.statusCode = code; return this; },
json(data) { this.data = data; return this; },
};
const postAccountHandler = cardavRouter.default.stack.find(
layer => layer.route?.path === '/accounts' && layer.route.methods.post
)?.route?.stack[0]?.handle;
await postAccountHandler(createReq, createRes);
const accountId = createRes.data.data.account.id;
// Sync the account
const req = {
params: { id: String(accountId) },
query: {},
body: {}
};
const res = {
statusCode: 200,
status(code) { this.statusCode = code; return this; },
json(data) { this.data = data; return this; },
};
const syncHandler = cardavRouter.default.stack.find(
layer => layer.route?.path === '/accounts/:id/sync' && layer.route.methods.post
)?.route?.stack[0]?.handle;
assert.ok(syncHandler, 'POST /accounts/:id/sync handler should exist');
await syncHandler(req, res);
assert.strictEqual(res.statusCode, 200);
assert.ok('synced' in res.data.data);
assert.ok('contactsAdded' in res.data.data);
assert.ok('contactsUpdated' in res.data.data);
});
it('POST /accounts/:id/sync - should return 404 for non-existent account', async () => {
const cardavRouter = await import('./server/routes/cardav.js');
const req = {
params: { id: '99999' },
query: {},
body: {}
};
const res = {
statusCode: 200,
status(code) { this.statusCode = code; return this; },
json(data) { this.data = data; return this; },
};
const syncHandler = cardavRouter.default.stack.find(
layer => layer.route?.path === '/accounts/:id/sync' && layer.route.methods.post
)?.route?.stack[0]?.handle;
await syncHandler(req, res);
assert.strictEqual(res.statusCode, 404);
assert.ok(res.data.error);
});
});
});