Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

253 lines
6.8 KiB
TypeScript

/**
* SQLite Reader Unit Tests
*
* Tests for reading data from SQLite databases:
* - Initialization with database path validation
* - Error handling for missing files/tables
* - Data transformation for cloud sync
* - Record count operations
*
* Note: Complex database operations are mocked to avoid CI issues.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as path from 'path';
// Mock fs module before imports
const mockExistsSync = vi.fn();
vi.mock('fs', () => ({
existsSync: (...args: unknown[]) => mockExistsSync(...args),
}));
// Create mock statement and database components
const mockStmtAll = vi.fn();
const mockStmtGet = vi.fn();
const mockPragma = vi.fn();
const mockPrepare = vi.fn();
const mockClose = vi.fn();
// Mock database instance
const createMockDb = () => ({
pragma: mockPragma,
prepare: mockPrepare,
close: mockClose,
});
// Mock better-sqlite3 default export
vi.mock('better-sqlite3', () => {
const MockDatabase = vi.fn(() => createMockDb());
return { default: MockDatabase };
});
import {
SQLiteReader,
createSQLiteReader,
type SQLiteReaderConfig,
} from '../../../../src/sync/readers/sqlite-reader.js';
import type { SyncSource } from '../../../../src/sync/interfaces.js';
describe('SQLiteReader', () => {
const defaultSource: SyncSource = {
name: 'test-source',
type: 'sqlite',
path: 'test.db',
targetTable: 'aqe.test_table',
priority: 'high',
mode: 'full',
enabled: true,
};
const defaultConfig: SQLiteReaderConfig = {
source: defaultSource,
baseDir: '/test/base',
environment: 'test-env',
};
let reader: SQLiteReader;
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
// Default mock implementations
mockExistsSync.mockReturnValue(true);
mockStmtAll.mockReturnValue([]);
mockStmtGet.mockReturnValue({ count: 0 });
mockPrepare.mockReturnValue({
all: mockStmtAll,
get: mockStmtGet,
});
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
});
afterEach(() => {
consoleLogSpy.mockRestore();
consoleWarnSpy.mockRestore();
});
describe('constructor', () => {
it('should create reader with config', () => {
reader = new SQLiteReader(defaultConfig);
expect(reader.name).toBe('test-source');
expect(reader.type).toBe('sqlite');
});
it('should resolve path relative to baseDir', () => {
reader = new SQLiteReader(defaultConfig);
const expectedPath = path.resolve('/test/base', 'test.db');
const info = reader.getInfo();
expect(info.path).toBe(expectedPath);
});
});
describe('initialize', () => {
it('should throw error if database file not found', async () => {
mockExistsSync.mockReturnValue(false);
reader = new SQLiteReader(defaultConfig);
await expect(reader.initialize()).rejects.toThrow('SQLite database not found');
});
});
describe('readAll', () => {
it('should throw error if not initialized', async () => {
const uninitReader = new SQLiteReader(defaultConfig);
await expect(uninitReader.readAll()).rejects.toThrow('Reader not initialized');
});
});
describe('readChanged', () => {
it('should throw error if not initialized', async () => {
const uninitReader = new SQLiteReader(defaultConfig);
await expect(uninitReader.readChanged(new Date())).rejects.toThrow(
'Reader not initialized'
);
});
});
describe('count', () => {
it('should throw error if not initialized', async () => {
const uninitReader = new SQLiteReader(defaultConfig);
await expect(uninitReader.count()).rejects.toThrow('Reader not initialized');
});
});
describe('close', () => {
it('should handle close when not initialized', async () => {
reader = new SQLiteReader(defaultConfig);
await expect(reader.close()).resolves.not.toThrow();
});
});
describe('getInfo', () => {
it('should handle missing database file', () => {
mockExistsSync.mockReturnValue(false);
reader = new SQLiteReader(defaultConfig);
const info = reader.getInfo();
expect(info.exists).toBe(false);
expect(info.tables).toEqual([]);
});
it('should return correct path', () => {
reader = new SQLiteReader(defaultConfig);
const info = reader.getInfo();
expect(info.path).toBe(path.resolve('/test/base', 'test.db'));
});
});
});
describe('createSQLiteReader', () => {
it('should create SQLiteReader instance', () => {
const config: SQLiteReaderConfig = {
source: {
name: 'test',
type: 'sqlite',
path: 'test.db',
targetTable: 'test_table',
priority: 'high',
mode: 'full',
},
baseDir: '/test',
environment: 'test',
};
const reader = createSQLiteReader(config);
expect(reader).toBeInstanceOf(SQLiteReader);
});
});
describe('SQLiteReader data transformation helpers', () => {
// Test the transformation logic through the reader's public interface
// These tests validate the transformation rules without requiring a full database
it('should configure reader for JSON parsing', () => {
const reader = new SQLiteReader({
source: {
name: 'test',
type: 'sqlite',
path: 'test.db',
targetTable: 'aqe.test_table',
priority: 'high',
mode: 'full',
},
baseDir: '/test',
environment: 'test-env',
});
expect(reader.name).toBe('test');
expect(reader.type).toBe('sqlite');
});
it('should configure reader with custom query', () => {
const reader = new SQLiteReader({
source: {
name: 'custom-query-source',
type: 'sqlite',
path: 'test.db',
targetTable: 'aqe.custom_table',
priority: 'medium',
mode: 'incremental',
query: 'SELECT id, name FROM custom_table WHERE active = 1',
},
baseDir: '/test',
environment: 'production',
});
expect(reader.name).toBe('custom-query-source');
});
it('should extract table name from target table', () => {
const reader = new SQLiteReader({
source: {
name: 'schema-test',
type: 'sqlite',
path: 'test.db',
targetTable: 'schema.table_name',
priority: 'high',
mode: 'full',
},
baseDir: '/test',
environment: 'test',
});
// The table extraction logic is internal but affects readAll behavior
expect(reader).toBeDefined();
});
});