Files
oikos/test-caldav-sync.js
T
Copilot 21c85fd418 fix: remap apple external_source during CalDAV migration
Fixes #119

Moves apple→caldav conversion into table rebuild to avoid CHECK constraint violation during migration to v0.44.0.
2026-05-04 16:38:07 +02:00

193 lines
7.2 KiB
JavaScript

/**
* Test: CalDAV Multi-Account Sync
* Purpose: Verify CalDAV multi-account functionality
*/
import { describe, it, before } from 'node:test';
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
const TEST_DB = ':memory:';
describe('CalDAV Multi-Account Sync', () => {
let db;
before(() => {
// Create in-memory DB
db = new DatabaseSync(TEST_DB);
// Create tables (simplified schema for testing)
db.exec(`
CREATE TABLE caldav_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
caldav_url TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_sync TEXT,
UNIQUE(caldav_url, username)
);
CREATE TABLE caldav_calendar_selection (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
calendar_url TEXT NOT NULL,
calendar_name TEXT NOT NULL,
calendar_color TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (account_id) REFERENCES caldav_accounts(id) ON DELETE CASCADE,
UNIQUE(account_id, calendar_url)
);
CREATE TABLE calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
external_calendar_id TEXT,
external_source TEXT,
target_caldav_account_id INTEGER,
target_caldav_calendar_url TEXT
);
CREATE TABLE external_calendars (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
external_id TEXT NOT NULL,
name TEXT NOT NULL,
color TEXT,
UNIQUE(source, external_id)
);
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL
);
INSERT INTO users (username) VALUES ('testuser');
`);
});
it('should create caldav_accounts table with correct schema', () => {
const result = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='caldav_accounts'").get();
assert.ok(result, 'caldav_accounts table should exist');
});
it('should create caldav_calendar_selection table with FK', () => {
const result = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='caldav_calendar_selection'").get();
assert.ok(result, 'caldav_calendar_selection table should exist');
});
it('should have target columns in calendar_events', () => {
const cols = db.prepare("PRAGMA table_info(calendar_events)").all();
const colNames = cols.map(c => c.name);
assert.ok(colNames.includes('target_caldav_account_id'), 'Should have target_caldav_account_id column');
assert.ok(colNames.includes('target_caldav_calendar_url'), 'Should have target_caldav_calendar_url column');
});
it('should insert account and enforce UNIQUE constraint', () => {
db.prepare(`
INSERT INTO caldav_accounts (name, caldav_url, username, password)
VALUES (?, ?, ?, ?)
`).run('Test Account', 'https://caldav.example.com', 'user', 'pass');
const account = db.prepare('SELECT * FROM caldav_accounts WHERE name = ?').get('Test Account');
assert.ok(account, 'Account should be inserted');
assert.strictEqual(account.caldav_url, 'https://caldav.example.com');
// Duplicate should fail
assert.throws(() => {
db.prepare(`
INSERT INTO caldav_accounts (name, caldav_url, username, password)
VALUES (?, ?, ?, ?)
`).run('Duplicate', 'https://caldav.example.com', 'user', 'pass');
}, 'UNIQUE constraint should prevent duplicates');
});
it('should insert calendar selection and link to account', () => {
const accountId = db.prepare('SELECT id FROM caldav_accounts WHERE name = ?').get('Test Account').id;
db.prepare(`
INSERT INTO caldav_calendar_selection (account_id, calendar_url, calendar_name, enabled)
VALUES (?, ?, ?, ?)
`).run(accountId, 'https://cal.example.com/cal1', 'Private', 1);
const calendar = db.prepare('SELECT * FROM caldav_calendar_selection WHERE account_id = ?').get(accountId);
assert.ok(calendar, 'Calendar should be inserted');
assert.strictEqual(calendar.calendar_name, 'Private');
assert.strictEqual(calendar.enabled, 1);
});
it('should CASCADE delete calendar_selection when account deleted', () => {
const accountId = db.prepare('SELECT id FROM caldav_accounts WHERE name = ?').get('Test Account').id;
// Delete account
db.prepare('DELETE FROM caldav_accounts WHERE id = ?').run(accountId);
// Calendar selection should be deleted
const remaining = db.prepare('SELECT * FROM caldav_calendar_selection WHERE account_id = ?').get(accountId);
assert.strictEqual(remaining, undefined, 'Calendar selection should be deleted via CASCADE');
});
it('should handle enabled/disabled calendar selection', () => {
// Insert new account
db.prepare(`
INSERT INTO caldav_accounts (name, caldav_url, username, password)
VALUES (?, ?, ?, ?)
`).run('Account 2', 'https://caldav2.example.com', 'user2', 'pass2');
const accountId = db.prepare('SELECT id FROM caldav_accounts WHERE name = ?').get('Account 2').id;
// Insert calendars
db.prepare(`
INSERT INTO caldav_calendar_selection (account_id, calendar_url, calendar_name, enabled)
VALUES (?, ?, ?, ?), (?, ?, ?, ?)
`).run(
accountId, 'https://cal.example.com/cal1', 'Private', 1,
accountId, 'https://cal.example.com/cal2', 'Work', 0
);
// Query only enabled
const enabled = db.prepare('SELECT * FROM caldav_calendar_selection WHERE account_id = ? AND enabled = 1').all(accountId);
assert.strictEqual(enabled.length, 1, 'Should have 1 enabled calendar');
assert.strictEqual(enabled[0].calendar_name, 'Private');
});
it('should migrate apple calendar events to caldav without violating CHECK', () => {
const db2 = new DatabaseSync(':memory:');
db2.exec(`
CREATE TABLE calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
external_source TEXT NOT NULL DEFAULT 'local'
CHECK(external_source IN ('local', 'google', 'apple', 'ics'))
);
`);
db2.prepare(`
INSERT INTO calendar_events (title, external_source)
VALUES ('Migrated', 'apple')
`).run();
db2.exec(`
CREATE TABLE calendar_events_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
external_source TEXT NOT NULL DEFAULT 'local'
CHECK(external_source IN ('local', 'google', 'apple', 'ics', 'caldav'))
);
`);
db2.exec(`
INSERT INTO calendar_events_new (id, title, external_source)
SELECT id, title,
CASE WHEN external_source = 'apple' THEN 'caldav' ELSE external_source END
FROM calendar_events
`);
const migrated = db2.prepare(`SELECT external_source FROM calendar_events_new WHERE title = 'Migrated'`).get();
assert.strictEqual(migrated.external_source, 'caldav');
});
});