285 lines
12 KiB
JavaScript
285 lines
12 KiB
JavaScript
/**
|
|
* Database Provider Tests - Cross-Platform Compatibility
|
|
*
|
|
* Tests for platform-aware database selection and fallback mechanisms
|
|
*/
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { createDatabase, getPlatformInfo, getAvailableProviders } from './database-provider.js';
|
|
import { createDefaultEntry } from './types.js';
|
|
import { unlinkSync, existsSync } from 'node:fs';
|
|
describe('DatabaseProvider', () => {
|
|
const testDbPath = './test-database-provider.db';
|
|
afterEach(() => {
|
|
// Cleanup test database
|
|
if (existsSync(testDbPath)) {
|
|
try {
|
|
unlinkSync(testDbPath);
|
|
}
|
|
catch (error) {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
});
|
|
describe('Platform Detection', () => {
|
|
it('should detect platform information', () => {
|
|
const info = getPlatformInfo();
|
|
expect(info).toHaveProperty('os');
|
|
expect(info).toHaveProperty('isWindows');
|
|
expect(info).toHaveProperty('isMacOS');
|
|
expect(info).toHaveProperty('isLinux');
|
|
expect(info).toHaveProperty('recommendedProvider');
|
|
// Should recommend sql.js on Windows, better-sqlite3 on Unix
|
|
if (info.isWindows) {
|
|
expect(info.recommendedProvider).toBe('sql.js');
|
|
}
|
|
else {
|
|
expect(info.recommendedProvider).toBe('better-sqlite3');
|
|
}
|
|
});
|
|
});
|
|
describe('Provider Availability', () => {
|
|
it('should check available providers', async () => {
|
|
const available = await getAvailableProviders();
|
|
expect(available).toHaveProperty('betterSqlite3');
|
|
expect(available).toHaveProperty('sqlJs');
|
|
expect(available).toHaveProperty('json');
|
|
// JSON backend should always be available
|
|
expect(available.json).toBe(true);
|
|
});
|
|
});
|
|
describe('Automatic Provider Selection', () => {
|
|
it('should create database with auto provider selection', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
expect(db).toBeDefined();
|
|
await expect(db.count()).resolves.toBe(0);
|
|
await db.shutdown();
|
|
});
|
|
it('should create persistent database with auto provider', async () => {
|
|
const db = await createDatabase(testDbPath);
|
|
expect(db).toBeDefined();
|
|
// Store test entry
|
|
const entry = createDefaultEntry({
|
|
key: 'test-key',
|
|
content: 'test content',
|
|
namespace: 'test',
|
|
});
|
|
await db.store(entry);
|
|
await expect(db.count()).resolves.toBe(1);
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
describe('Explicit Provider Selection', () => {
|
|
it('should create database with sql.js provider', async () => {
|
|
const available = await getAvailableProviders();
|
|
if (!available.sqlJs) {
|
|
console.log('sql.js not available, skipping test');
|
|
return;
|
|
}
|
|
const db = await createDatabase(':memory:', {
|
|
provider: 'sql.js',
|
|
verbose: false,
|
|
});
|
|
expect(db).toBeDefined();
|
|
// Test basic operations
|
|
const entry = createDefaultEntry({
|
|
key: 'sqljs-test',
|
|
content: 'testing sql.js backend',
|
|
namespace: 'test',
|
|
});
|
|
await db.store(entry);
|
|
await expect(db.count()).resolves.toBe(1);
|
|
const retrieved = await db.get(entry.id);
|
|
expect(retrieved).toBeDefined();
|
|
expect(retrieved?.key).toBe('sqljs-test');
|
|
await db.shutdown();
|
|
});
|
|
it('should create database with better-sqlite3 provider', async () => {
|
|
const available = await getAvailableProviders();
|
|
if (!available.betterSqlite3) {
|
|
console.log('better-sqlite3 not available, skipping test');
|
|
return;
|
|
}
|
|
const db = await createDatabase(':memory:', {
|
|
provider: 'better-sqlite3',
|
|
verbose: false,
|
|
});
|
|
expect(db).toBeDefined();
|
|
// Test basic operations
|
|
const entry = createDefaultEntry({
|
|
key: 'sqlite-test',
|
|
content: 'testing better-sqlite3 backend',
|
|
namespace: 'test',
|
|
});
|
|
await db.store(entry);
|
|
await expect(db.count()).resolves.toBe(1);
|
|
const retrieved = await db.get(entry.id);
|
|
expect(retrieved).toBeDefined();
|
|
expect(retrieved?.key).toBe('sqlite-test');
|
|
await db.shutdown();
|
|
});
|
|
it('should create database with JSON provider', async () => {
|
|
const db = await createDatabase(testDbPath, {
|
|
provider: 'json',
|
|
verbose: false,
|
|
});
|
|
expect(db).toBeDefined();
|
|
// Test basic operations
|
|
const entry = createDefaultEntry({
|
|
key: 'json-test',
|
|
content: 'testing JSON backend',
|
|
namespace: 'test',
|
|
});
|
|
await db.store(entry);
|
|
await expect(db.count()).resolves.toBe(1);
|
|
const retrieved = await db.get(entry.id);
|
|
expect(retrieved).toBeDefined();
|
|
expect(retrieved?.key).toBe('json-test');
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
describe('Cross-Platform Functionality', () => {
|
|
it('should handle CRUD operations consistently across providers', async () => {
|
|
const available = await getAvailableProviders();
|
|
const providers = [];
|
|
if (available.betterSqlite3)
|
|
providers.push('better-sqlite3');
|
|
if (available.sqlJs)
|
|
providers.push('sql.js');
|
|
providers.push('json'); // Always available
|
|
for (const provider of providers) {
|
|
const db = await createDatabase(':memory:', { provider });
|
|
// Create
|
|
const entry = createDefaultEntry({
|
|
key: `test-${provider}`,
|
|
content: `testing ${provider}`,
|
|
namespace: 'cross-platform',
|
|
tags: ['test', provider],
|
|
});
|
|
await db.store(entry);
|
|
// Read
|
|
const retrieved = await db.get(entry.id);
|
|
expect(retrieved).toBeDefined();
|
|
expect(retrieved?.key).toBe(`test-${provider}`);
|
|
expect(retrieved?.tags).toContain(provider);
|
|
// Update
|
|
const updated = await db.update(entry.id, {
|
|
content: `updated ${provider}`,
|
|
});
|
|
expect(updated).toBeDefined();
|
|
expect(updated?.content).toBe(`updated ${provider}`);
|
|
// Query
|
|
const results = await db.query({
|
|
type: 'hybrid',
|
|
namespace: 'cross-platform',
|
|
limit: 10,
|
|
});
|
|
expect(results.length).toBe(1);
|
|
expect(results[0].key).toBe(`test-${provider}`);
|
|
// Delete
|
|
const deleted = await db.delete(entry.id);
|
|
expect(deleted).toBe(true);
|
|
await expect(db.count()).resolves.toBe(0);
|
|
await db.shutdown();
|
|
}
|
|
});
|
|
it('should handle namespace operations across providers', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
// Create entries in different namespaces
|
|
const entries = [
|
|
createDefaultEntry({ key: 'entry1', content: 'content1', namespace: 'ns1' }),
|
|
createDefaultEntry({ key: 'entry2', content: 'content2', namespace: 'ns1' }),
|
|
createDefaultEntry({ key: 'entry3', content: 'content3', namespace: 'ns2' }),
|
|
];
|
|
for (const entry of entries) {
|
|
await db.store(entry);
|
|
}
|
|
// List namespaces
|
|
const namespaces = await db.listNamespaces();
|
|
expect(namespaces).toContain('ns1');
|
|
expect(namespaces).toContain('ns2');
|
|
// Count by namespace
|
|
await expect(db.count('ns1')).resolves.toBe(2);
|
|
await expect(db.count('ns2')).resolves.toBe(1);
|
|
// Clear namespace
|
|
const cleared = await db.clearNamespace('ns1');
|
|
expect(cleared).toBe(2);
|
|
await expect(db.count('ns1')).resolves.toBe(0);
|
|
await expect(db.count('ns2')).resolves.toBe(1);
|
|
await db.shutdown();
|
|
});
|
|
it('should handle bulk operations across providers', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
// Bulk insert
|
|
const entries = Array.from({ length: 10 }, (_, i) => createDefaultEntry({
|
|
key: `bulk-${i}`,
|
|
content: `content ${i}`,
|
|
namespace: 'bulk-test',
|
|
}));
|
|
await db.bulkInsert(entries);
|
|
await expect(db.count('bulk-test')).resolves.toBe(10);
|
|
// Bulk delete
|
|
const idsToDelete = entries.slice(0, 5).map((e) => e.id);
|
|
const deletedCount = await db.bulkDelete(idsToDelete);
|
|
expect(deletedCount).toBe(5);
|
|
await expect(db.count('bulk-test')).resolves.toBe(5);
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
describe('Health Check', () => {
|
|
it('should perform health check', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
const health = await db.healthCheck();
|
|
expect(health).toHaveProperty('status');
|
|
expect(health).toHaveProperty('components');
|
|
expect(health).toHaveProperty('timestamp');
|
|
expect(health).toHaveProperty('issues');
|
|
expect(health).toHaveProperty('recommendations');
|
|
expect(health.components).toHaveProperty('storage');
|
|
expect(health.components).toHaveProperty('index');
|
|
expect(health.components).toHaveProperty('cache');
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
describe('Statistics', () => {
|
|
it('should provide backend statistics', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
// Add some test data
|
|
const entries = [
|
|
createDefaultEntry({ key: 'stat1', content: 'content1', namespace: 'stats', type: 'semantic' }),
|
|
createDefaultEntry({ key: 'stat2', content: 'content2', namespace: 'stats', type: 'episodic' }),
|
|
createDefaultEntry({ key: 'stat3', content: 'content3', namespace: 'stats', type: 'semantic' }),
|
|
];
|
|
for (const entry of entries) {
|
|
await db.store(entry);
|
|
}
|
|
const stats = await db.getStats();
|
|
expect(stats).toHaveProperty('totalEntries');
|
|
expect(stats).toHaveProperty('entriesByNamespace');
|
|
expect(stats).toHaveProperty('entriesByType');
|
|
expect(stats).toHaveProperty('memoryUsage');
|
|
expect(stats).toHaveProperty('avgQueryTime');
|
|
expect(stats.totalEntries).toBe(3);
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
describe('Error Handling', () => {
|
|
it('should handle missing entries gracefully', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
const entry = await db.get('non-existent-id');
|
|
expect(entry).toBeNull();
|
|
const byKey = await db.getByKey('non-existent-ns', 'non-existent-key');
|
|
expect(byKey).toBeNull();
|
|
await db.shutdown();
|
|
});
|
|
it('should handle empty queries', async () => {
|
|
const db = await createDatabase(':memory:');
|
|
const results = await db.query({
|
|
type: 'hybrid',
|
|
limit: 10,
|
|
});
|
|
expect(results).toEqual([]);
|
|
await db.shutdown();
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=database-provider.test.js.map
|