fix: removed turso

This commit is contained in:
2026-02-11 08:42:10 +01:00
parent a8499626c0
commit f4ff91180d
7 changed files with 117 additions and 350 deletions

View File

@@ -86,6 +86,25 @@ vi.mock('../../src/main/engine/MediaEngine', () => ({
})),
}));
// Mock DropboxSyncEngine
let mockDropboxConfigured = false;
const mockDropboxSyncEngine = {
isConfigured: vi.fn(() => mockDropboxConfigured),
configure: vi.fn(async () => {}),
syncAll: vi.fn(async () => ({
success: true,
uploaded: 0,
downloaded: 0,
deleted: 0,
conflicts: 0,
errors: [],
})),
};
vi.mock('../../src/main/engine/DropboxSyncEngine', () => ({
getDropboxSyncEngine: vi.fn(() => mockDropboxSyncEngine),
}));
// Mock uuid
vi.mock('uuid', () => ({
v4: vi.fn(() => 'mock-sync-uuid-' + Math.random().toString(36).substr(2, 9)),
@@ -101,6 +120,10 @@ describe('SyncEngine', () => {
mockMedia.clear();
mockSyncLog.clear();
resetMockCounters();
// Reset Dropbox mock state
mockDropboxConfigured = false;
mockDropboxSyncEngine.isConfigured.mockImplementation(() => mockDropboxConfigured);
syncEngine = new SyncEngine();
});
@@ -131,9 +154,10 @@ describe('SyncEngine', () => {
describe('Configuration', () => {
it('should configure sync settings', async () => {
// Mark Dropbox as configured
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -148,8 +172,6 @@ describe('SyncEngine', () => {
syncEngine.on('configured', handler);
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -159,23 +181,10 @@ describe('SyncEngine', () => {
expect(handler).toHaveBeenCalledWith(config);
});
it('should not be configured with empty URL', async () => {
it('should not be configured when Dropbox is not configured', async () => {
mockDropboxConfigured = false;
const config: SyncConfig = {
tursoUrl: '',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
await syncEngine.configure(config);
expect(syncEngine.isConfigured()).toBe(false);
});
it('should not be configured with empty token', async () => {
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: '',
autoSync: false,
syncInterval: 30,
};
@@ -188,9 +197,9 @@ describe('SyncEngine', () => {
describe('Auto Sync', () => {
it('should start auto sync when enabled', async () => {
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 1, // 1 minute
};
@@ -205,9 +214,8 @@ describe('SyncEngine', () => {
const handler = vi.fn();
syncEngine.on('autoSyncStopped', handler);
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 1,
};
@@ -219,16 +227,14 @@ describe('SyncEngine', () => {
});
it('should stop previous auto sync when reconfiguring', async () => {
mockDropboxConfigured = true;
const config1: SyncConfig = {
tursoUrl: 'libsql://test1.turso.io',
tursoAuthToken: 'test-token-1',
autoSync: true,
syncInterval: 1,
};
const config2: SyncConfig = {
tursoUrl: 'libsql://test2.turso.io',
tursoAuthToken: 'test-token-2',
autoSync: true,
syncInterval: 5,
};
@@ -251,7 +257,7 @@ describe('SyncEngine', () => {
const result = await syncEngine.sync('bidirectional');
expect(result.success).toBe(false);
expect(result.errors).toContain('Sync not configured');
expect(result.errors).toContain('Dropbox sync not configured');
});
it('should return zero counts when not configured', async () => {
@@ -393,19 +399,10 @@ describe('SyncEngine', () => {
});
describe('Sync Configuration Validation', () => {
it('should require both URL and token', async () => {
it('should not be configured when Dropbox is not set up', async () => {
mockDropboxConfigured = false;
await syncEngine.configure({
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: '',
autoSync: false,
syncInterval: 30,
});
expect(syncEngine.isConfigured()).toBe(false);
await syncEngine.configure({
tursoUrl: '',
tursoAuthToken: 'token',
autoSync: false,
syncInterval: 30,
});
@@ -413,10 +410,10 @@ describe('SyncEngine', () => {
expect(syncEngine.isConfigured()).toBe(false);
});
it('should be configured with valid URL and token', async () => {
it('should be configured when Dropbox is set up', async () => {
mockDropboxConfigured = true;
await syncEngine.configure({
tursoUrl: 'libsql://valid.turso.io',
tursoAuthToken: 'valid-token',
autoSync: false,
syncInterval: 30,
});
@@ -427,9 +424,9 @@ describe('SyncEngine', () => {
describe('Sync Interval Configuration', () => {
it('should accept sync interval in minutes', async () => {
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 15, // 15 minutes
};
@@ -439,9 +436,9 @@ describe('SyncEngine', () => {
});
it('should not set auto sync with zero interval', async () => {
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 0,
};
@@ -452,112 +449,6 @@ describe('SyncEngine', () => {
});
});
describe('Remote Schema Migration', () => {
it('should run migrations on remote database when initializing', async () => {
const { getDatabase } = await import('../../src/main/database');
const mockRunRemoteMigrations = vi.fn().mockResolvedValue(undefined);
vi.mocked(getDatabase).mockReturnValue({
getLocal: vi.fn(() => mockLocalDb),
getLocalClient: vi.fn(() => null),
getRemote: vi.fn(() => mockRemoteDb),
getDataPaths: vi.fn(() => ({
database: '/mock/userData/bds.db',
posts: '/mock/userData/posts',
media: '/mock/userData/media',
})),
initializeLocal: vi.fn(),
initializeRemote: vi.fn(async () => {}),
runRemoteMigrations: mockRunRemoteMigrations,
close: vi.fn(),
} as any);
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
await syncEngine.configure(config);
expect(mockRunRemoteMigrations).toHaveBeenCalled();
});
});
describe('Sync Timeout', () => {
it('should timeout if sync takes too long', async () => {
const { getDatabase } = await import('../../src/main/database');
// Create a mock that never resolves for remote operations
const hangingInsert = vi.fn(() => ({
values: vi.fn(() => ({
onConflictDoUpdate: vi.fn(() => new Promise(() => {})), // Never resolves
})),
}));
const hangingRemoteDb = {
...mockRemoteDb,
insert: hangingInsert,
};
vi.mocked(getDatabase).mockReturnValue({
getLocal: vi.fn(() => ({
...mockLocalDb,
select: vi.fn(() => ({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(),
offset: vi.fn().mockReturnThis(),
all: vi.fn().mockResolvedValue([{ id: 'test-post', title: 'Test', syncStatus: 'pending' }]),
})),
update: vi.fn(() => ({
set: vi.fn(() => ({
where: vi.fn(() => Promise.resolve()),
})),
})),
})),
getLocalClient: vi.fn(() => null),
getRemote: vi.fn(() => hangingRemoteDb),
getDataPaths: vi.fn(() => ({
database: '/mock/userData/bds.db',
posts: '/mock/userData/posts',
media: '/mock/userData/media',
})),
initializeLocal: vi.fn(),
initializeRemote: vi.fn(async () => {}),
runRemoteMigrations: vi.fn(async () => {}),
close: vi.fn(),
} as any);
// Use real timers and set a short timeout before configuring
vi.useRealTimers();
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
await syncEngine.configure(config);
// Set a short timeout for the test (100ms)
syncEngine.setSyncTimeout(100);
// This should timeout rather than hang forever
const result = await syncEngine.sync('push');
expect(result.success).toBe(false);
expect(result.errors.some((e: string) => e.includes('timeout') || e.includes('Timeout'))).toBe(true);
}, 15000); // Extend test timeout to 15 seconds
it('should have configurable timeout', () => {
expect(typeof syncEngine.getSyncTimeout).toBe('function');
expect(typeof syncEngine.setSyncTimeout).toBe('function');
});
});
describe('SyncStatus Reset on Failure', () => {
it('should reset syncStatus to idle when task manager throws', async () => {
const { getDatabase } = await import('../../src/main/database');
@@ -582,9 +473,11 @@ describe('SyncEngine', () => {
close: vi.fn(),
} as any);
mockDropboxConfigured = true;
// Mock Dropbox sync to fail
mockDropboxSyncEngine.syncAll.mockRejectedValue(new Error('Database exploded'));
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -597,6 +490,9 @@ describe('SyncEngine', () => {
// Status should be reset to allow future syncs
expect(syncEngine.getSyncStatus()).not.toBe('syncing');
// Reset mock for second call
mockDropboxSyncEngine.syncAll.mockRejectedValue(new Error('Database exploded'));
// A subsequent sync should not return "Sync already in progress"
const result = await syncEngine.sync('push');
expect(result.errors).not.toContain('Sync already in progress');
@@ -625,9 +521,11 @@ describe('SyncEngine', () => {
close: vi.fn(),
} as any);
mockDropboxConfigured = true;
// Mock Dropbox sync to fail
mockDropboxSyncEngine.syncAll.mockRejectedValue(new Error('Database error'));
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -645,9 +543,8 @@ describe('SyncEngine', () => {
it('should log sync start with direction', async () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -691,9 +588,8 @@ describe('SyncEngine', () => {
close: vi.fn(),
} as any);
mockDropboxConfigured = true;
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -737,9 +633,11 @@ describe('SyncEngine', () => {
close: vi.fn(),
} as any);
mockDropboxConfigured = true;
// Mock Dropbox sync to fail
mockDropboxSyncEngine.syncAll.mockRejectedValue(new Error('Test error for logging'));
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
@@ -755,104 +653,4 @@ describe('SyncEngine', () => {
consoleErrorSpy.mockRestore();
});
});
describe('Batch Size Configuration', () => {
it('should have configurable batch size', () => {
expect(typeof syncEngine.getBatchSize).toBe('function');
expect(typeof syncEngine.setBatchSize).toBe('function');
});
it('should default to 50 items per batch', () => {
expect(syncEngine.getBatchSize()).toBe(50);
});
it('should allow setting custom batch size', () => {
syncEngine.setBatchSize(100);
expect(syncEngine.getBatchSize()).toBe(100);
// Reset to default
syncEngine.setBatchSize(50);
});
it('should process posts in batches', async () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const { getDatabase } = await import('../../src/main/database');
// Create 150 mock posts (should result in 3 batches with batch size 50)
const mockPendingPosts = Array.from({ length: 150 }, (_, i) => ({
id: `post-${i}`,
title: `Post ${i}`,
slug: `post-${i}`,
syncStatus: 'pending',
projectId: 'default',
createdAt: new Date(),
updatedAt: new Date(),
}));
let queryCount = 0;
vi.mocked(getDatabase).mockReturnValue({
getLocal: vi.fn(() => ({
...mockLocalDb,
select: vi.fn(() => ({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(),
offset: vi.fn().mockImplementation((offset: number) => ({
all: vi.fn().mockImplementation(() => {
queryCount++;
const batch = mockPendingPosts.slice(offset, offset + 50);
return Promise.resolve(batch);
}),
})),
all: vi.fn().mockResolvedValue([]), // For media query
})),
update: vi.fn(() => ({
set: vi.fn(() => ({
where: vi.fn(() => Promise.resolve()),
})),
})),
insert: vi.fn(() => ({
values: vi.fn(() => Promise.resolve()),
})),
})),
getLocalClient: vi.fn(() => null),
getRemote: vi.fn(() => ({
...mockRemoteDb,
insert: vi.fn(() => ({
values: vi.fn(() => ({
onConflictDoUpdate: vi.fn(() => Promise.resolve()),
})),
})),
})),
getDataPaths: vi.fn(() => ({
database: '/mock/userData/bds.db',
posts: '/mock/userData/posts',
media: '/mock/userData/media',
})),
initializeLocal: vi.fn(),
initializeRemote: vi.fn(async () => {}),
runRemoteMigrations: vi.fn(async () => {}),
close: vi.fn(),
} as any);
const config: SyncConfig = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: false,
syncInterval: 30,
};
await syncEngine.configure(config);
syncEngine.setBatchSize(50);
await syncEngine.sync('push');
// Should have logged batch progress
const calls = consoleSpy.mock.calls;
const hasBatchLog = calls.some((call: any[]) =>
call.some((arg: any) => typeof arg === 'string' && arg.includes('batch'))
);
expect(hasBatchLog).toBe(true);
consoleSpy.mockRestore();
});
});
});

View File

@@ -68,19 +68,6 @@ describe('SettingsView Behavior', () => {
});
describe('Credentials Storage (localStorage)', () => {
it('should save Turso credentials to localStorage', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
const saved = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
expect(saved.tursoUrl).toBe('libsql://test.turso.io');
expect(saved.tursoToken).toBe('test-token');
});
it('should save Dropbox credentials to localStorage', () => {
const creds = {
dropboxAccessToken: 'dbx-token',
@@ -98,48 +85,24 @@ describe('SettingsView Behavior', () => {
it('should load credentials from localStorage', () => {
const creds = {
tursoUrl: 'libsql://saved.turso.io',
tursoToken: 'saved-token',
dropboxAccessToken: 'saved-dbx-token',
dropboxAppKey: 'saved-key',
dropboxRemotePath: '/blog',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
expect(loaded.tursoUrl).toBe('libsql://saved.turso.io');
expect(loaded.dropboxAccessToken).toBe('saved-dbx-token');
});
it('should handle clearing Turso credentials independently', () => {
it('should handle clearing Dropbox credentials', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
dropboxAccessToken: 'dbx-token',
dropboxAppKey: 'dbx-key',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
// Clear only Turso credentials
const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
const cleared = { ...loaded, tursoUrl: '', tursoToken: '' };
localStorage.setItem('bds-credentials', JSON.stringify(cleared));
const result = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
expect(result.tursoUrl).toBe('');
expect(result.tursoToken).toBe('');
// Dropbox credentials should be untouched
expect(result.dropboxAccessToken).toBe('dbx-token');
expect(result.dropboxAppKey).toBe('dbx-key');
});
it('should handle clearing Dropbox credentials independently', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
dropboxAccessToken: 'dbx-token',
dropboxAppKey: 'dbx-key',
dropboxRemotePath: '/blog',
ftpHost: 'ftp.example.com',
ftpUser: 'user',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
@@ -155,11 +118,11 @@ describe('SettingsView Behavior', () => {
localStorage.setItem('bds-credentials', JSON.stringify(cleared));
const result = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
// Turso credentials should be untouched
expect(result.tursoUrl).toBe('libsql://test.turso.io');
expect(result.tursoToken).toBe('test-token');
expect(result.dropboxAccessToken).toBe('');
expect(result.dropboxAppKey).toBe('');
// FTP credentials should be untouched
expect(result.ftpHost).toBe('ftp.example.com');
expect(result.ftpUser).toBe('user');
});
});
@@ -231,8 +194,6 @@ describe('SettingsView Behavior', () => {
(window as any).electronAPI.sync.configure = mockConfigure;
const config = {
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 5,
};
@@ -240,8 +201,6 @@ describe('SettingsView Behavior', () => {
await window.electronAPI?.sync.configure(config);
expect(mockConfigure).toHaveBeenCalledWith({
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 5,
});