mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
b3d2018ee2
Critical bug fixes and code quality improvements from brutal-honest-review. ## Critical Fixes (9/10 completed, 1 deferred) 1. Fixed Agent Pattern Isolation (CRITICAL) - Added SwarmMemoryManager.queryPatternsByAgent() - SQL filtering by agent_id in metadata - Each agent now isolated 2. Fixed Async Method Cascade - Changed getPatterns() to async - Fixed 10 callers across 6 files - Build passes 3. Fixed Misleading Logging - Added isRealAgentDB() detection - Honest mock vs real reporting 4. Consolidated Embedding Generation - Created EmbeddingGenerator utility - Removed 50+ duplicate lines 5. Added AgentDB Initialization Checks - Empty DB detection - HNSW index verification 6. Integration Test Suite - 26 new test cases - Error path coverage 7. Repository Cleanup - Removed 19 temp test files 8. New QE Skill: sherlock-review - Evidence-based investigative review - 38 QE skills total 9. Deferred: Wire Up Real Test Execution - Deferred to v1.9.0 (documented) ## Statistics - Fixes: 9/10 (90%, 1 deferred) - Files Modified: 16 - Files Created: 3 - Files Deleted: 19 - Integration Tests: 26 - Build: PASSING ## Breaking Changes - getPatterns() now async - add await 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
501 lines
15 KiB
TypeScript
501 lines
15 KiB
TypeScript
/**
|
|
* Data Integrity Tests
|
|
* Tests complete data preservation, query performance, and vector embeddings
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import Database from 'better-sqlite3';
|
|
import * as crypto from 'crypto';
|
|
|
|
describe('Data Integrity - Integration Tests', () => {
|
|
const testDir = path.join(__dirname, '../.tmp/data-integrity-test-' + Date.now());
|
|
const sourceDbPath = path.join(testDir, 'source.db');
|
|
const targetDbPath = path.join(testDir, 'target.db');
|
|
|
|
beforeAll(() => {
|
|
fs.mkdirSync(testDir, { recursive: true });
|
|
|
|
// Create comprehensive test database
|
|
const db = new Database(sourceDbPath);
|
|
|
|
db.exec(`
|
|
-- Episodes table (ReasoningBank)
|
|
CREATE TABLE episodes (
|
|
id TEXT PRIMARY KEY,
|
|
session_id TEXT NOT NULL,
|
|
task TEXT NOT NULL,
|
|
input TEXT,
|
|
output TEXT,
|
|
critique TEXT,
|
|
reward REAL,
|
|
success INTEGER,
|
|
tokens_used INTEGER,
|
|
latency_ms INTEGER,
|
|
created_at INTEGER
|
|
);
|
|
|
|
-- Patterns table
|
|
CREATE TABLE patterns (
|
|
id TEXT PRIMARY KEY,
|
|
pattern_type TEXT NOT NULL,
|
|
pattern_data TEXT NOT NULL,
|
|
agent_id TEXT,
|
|
domain TEXT DEFAULT 'general',
|
|
success_rate REAL DEFAULT 1.0,
|
|
usage_count INTEGER DEFAULT 0,
|
|
created_at INTEGER
|
|
);
|
|
|
|
-- Learning experiences table
|
|
CREATE TABLE learning_experiences (
|
|
id TEXT PRIMARY KEY,
|
|
agent_id TEXT NOT NULL,
|
|
state TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
reward REAL,
|
|
next_state TEXT,
|
|
metadata TEXT,
|
|
created_at INTEGER
|
|
);
|
|
|
|
-- Q-values table
|
|
CREATE TABLE q_values (
|
|
state_action TEXT PRIMARY KEY,
|
|
agent_id TEXT NOT NULL,
|
|
q_value REAL,
|
|
visits INTEGER DEFAULT 0,
|
|
metadata TEXT
|
|
);
|
|
|
|
-- Indexes for performance
|
|
CREATE INDEX idx_episodes_session ON episodes(session_id);
|
|
CREATE INDEX idx_episodes_task ON episodes(task);
|
|
CREATE INDEX idx_patterns_agent ON patterns(agent_id);
|
|
CREATE INDEX idx_patterns_domain ON patterns(domain);
|
|
CREATE INDEX idx_learning_agent ON learning_experiences(agent_id);
|
|
CREATE INDEX idx_qvalues_agent ON q_values(agent_id);
|
|
`);
|
|
|
|
// Insert comprehensive test data
|
|
const insertData = db.prepare(`
|
|
INSERT INTO episodes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
for (let i = 1; i <= 100; i++) {
|
|
insertData.run(
|
|
`ep${i}`,
|
|
`session${Math.floor(i / 10) + 1}`,
|
|
`task-${i % 5}`,
|
|
`input-${i}`,
|
|
`output-${i}`,
|
|
`critique-${i}`,
|
|
Math.random(),
|
|
i % 2,
|
|
1000 + i * 10,
|
|
500 + i * 5,
|
|
Date.now() + i
|
|
);
|
|
}
|
|
|
|
const insertPatterns = db.prepare(`
|
|
INSERT INTO patterns VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
for (let i = 1; i <= 50; i++) {
|
|
insertPatterns.run(
|
|
`pat${i}`,
|
|
`pattern-type-${i % 3}`,
|
|
JSON.stringify({ strategy: `strategy-${i}`, confidence: Math.random() }),
|
|
`agent-${i % 5}`,
|
|
`domain-${i % 3}`,
|
|
0.5 + Math.random() * 0.5,
|
|
i * 2,
|
|
Date.now() + i
|
|
);
|
|
}
|
|
|
|
db.close();
|
|
|
|
// Create migration (copy to target)
|
|
fs.copyFileSync(sourceDbPath, targetDbPath);
|
|
});
|
|
|
|
afterAll(() => {
|
|
if (fs.existsSync(testDir)) {
|
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('Complete Data Migration', () => {
|
|
it('should migrate all episodes records', () => {
|
|
const sourceDb = new Database(sourceDbPath, { readonly: true });
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const sourceCount = sourceDb.prepare('SELECT COUNT(*) as count FROM episodes').get() as { count: number };
|
|
const targetCount = targetDb.prepare('SELECT COUNT(*) as count FROM episodes').get() as { count: number };
|
|
|
|
const sourceData = sourceDb.prepare('SELECT * FROM episodes ORDER BY id').all();
|
|
const targetData = targetDb.prepare('SELECT * FROM episodes ORDER BY id').all();
|
|
|
|
sourceDb.close();
|
|
targetDb.close();
|
|
|
|
expect(targetCount.count).toBe(sourceCount.count);
|
|
expect(targetCount.count).toBe(100);
|
|
expect(targetData).toEqual(sourceData);
|
|
});
|
|
|
|
it('should migrate all patterns records', () => {
|
|
const sourceDb = new Database(sourceDbPath, { readonly: true });
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const sourceCount = sourceDb.prepare('SELECT COUNT(*) as count FROM patterns').get() as { count: number };
|
|
const targetCount = targetDb.prepare('SELECT COUNT(*) as count FROM patterns').get() as { count: number };
|
|
|
|
sourceDb.close();
|
|
targetDb.close();
|
|
|
|
expect(targetCount.count).toBe(sourceCount.count);
|
|
expect(targetCount.count).toBe(50);
|
|
});
|
|
|
|
it('should preserve JSON data integrity', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const patterns = targetDb.prepare('SELECT pattern_data FROM patterns LIMIT 10').all() as Array<{ pattern_data: string }>;
|
|
|
|
targetDb.close();
|
|
|
|
patterns.forEach(pattern => {
|
|
expect(() => JSON.parse(pattern.pattern_data)).not.toThrow();
|
|
|
|
const data = JSON.parse(pattern.pattern_data);
|
|
expect(data).toHaveProperty('strategy');
|
|
expect(data).toHaveProperty('confidence');
|
|
});
|
|
});
|
|
|
|
it('should preserve numeric precision', () => {
|
|
const sourceDb = new Database(sourceDbPath, { readonly: true });
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const sourceRewards = sourceDb.prepare('SELECT reward FROM episodes ORDER BY id').all() as Array<{ reward: number }>;
|
|
const targetRewards = targetDb.prepare('SELECT reward FROM episodes ORDER BY id').all() as Array<{ reward: number }>;
|
|
|
|
sourceDb.close();
|
|
targetDb.close();
|
|
|
|
sourceRewards.forEach((source, index) => {
|
|
expect(targetRewards[index].reward).toBeCloseTo(source.reward, 10);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('No Data Corruption', () => {
|
|
it('should have matching checksums for identical data', () => {
|
|
function calculateTableChecksum(dbPath: string, tableName: string): string {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
const data = db.prepare(`SELECT * FROM ${tableName} ORDER BY id`).all();
|
|
db.close();
|
|
|
|
const hash = crypto.createHash('sha256');
|
|
hash.update(JSON.stringify(data));
|
|
return hash.digest('hex');
|
|
}
|
|
|
|
const sourceEpisodesChecksum = calculateTableChecksum(sourceDbPath, 'episodes');
|
|
const targetEpisodesChecksum = calculateTableChecksum(targetDbPath, 'episodes');
|
|
|
|
const sourcePatternsChecksum = calculateTableChecksum(sourceDbPath, 'patterns');
|
|
const targetPatternsChecksum = calculateTableChecksum(targetDbPath, 'patterns');
|
|
|
|
expect(targetEpisodesChecksum).toBe(sourceEpisodesChecksum);
|
|
expect(targetPatternsChecksum).toBe(sourcePatternsChecksum);
|
|
});
|
|
|
|
it('should preserve all column data types', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const episodesInfo = targetDb.prepare('PRAGMA table_info(episodes)').all() as Array<{
|
|
name: string;
|
|
type: string;
|
|
}>;
|
|
|
|
const patternsInfo = targetDb.prepare('PRAGMA table_info(patterns)').all() as Array<{
|
|
name: string;
|
|
type: string;
|
|
}>;
|
|
|
|
targetDb.close();
|
|
|
|
// Check episodes types
|
|
const episodesTypes: Record<string, string> = {};
|
|
episodesInfo.forEach(col => {
|
|
episodesTypes[col.name] = col.type;
|
|
});
|
|
|
|
expect(episodesTypes['id']).toBe('TEXT');
|
|
expect(episodesTypes['reward']).toBe('REAL');
|
|
expect(episodesTypes['success']).toBe('INTEGER');
|
|
|
|
// Check patterns types
|
|
const patternsTypes: Record<string, string> = {};
|
|
patternsInfo.forEach(col => {
|
|
patternsTypes[col.name] = col.type;
|
|
});
|
|
|
|
expect(patternsTypes['id']).toBe('TEXT');
|
|
expect(patternsTypes['success_rate']).toBe('REAL');
|
|
expect(patternsTypes['usage_count']).toBe('INTEGER');
|
|
});
|
|
|
|
it('should have no NULL corruption in NOT NULL fields', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const nullEpisodes = targetDb.prepare(`
|
|
SELECT COUNT(*) as count FROM episodes
|
|
WHERE session_id IS NULL OR task IS NULL
|
|
`).get() as { count: number };
|
|
|
|
const nullPatterns = targetDb.prepare(`
|
|
SELECT COUNT(*) as count FROM patterns
|
|
WHERE pattern_type IS NULL OR pattern_data IS NULL
|
|
`).get() as { count: number };
|
|
|
|
targetDb.close();
|
|
|
|
expect(nullEpisodes.count).toBe(0);
|
|
expect(nullPatterns.count).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Indexes Working Correctly', () => {
|
|
it('should preserve all indexes', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const indexes = targetDb.prepare(`
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='index' AND sql IS NOT NULL
|
|
`).all() as Array<{ name: string }>;
|
|
|
|
targetDb.close();
|
|
|
|
const indexNames = indexes.map(idx => idx.name);
|
|
|
|
expect(indexNames).toContain('idx_episodes_session');
|
|
expect(indexNames).toContain('idx_episodes_task');
|
|
expect(indexNames).toContain('idx_patterns_agent');
|
|
expect(indexNames).toContain('idx_patterns_domain');
|
|
});
|
|
|
|
it('should use indexes for queries', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
// Query that should use index
|
|
const explain = targetDb.prepare(`
|
|
EXPLAIN QUERY PLAN
|
|
SELECT * FROM episodes WHERE session_id = 'session1'
|
|
`).all();
|
|
|
|
targetDb.close();
|
|
|
|
const explainText = JSON.stringify(explain);
|
|
// SQLite query planner should mention the index
|
|
expect(explainText.toLowerCase()).toMatch(/idx_episodes_session|search|using index/);
|
|
});
|
|
|
|
it('should maintain index integrity', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
// Test each index works
|
|
const sessionQuery = targetDb.prepare('SELECT * FROM episodes WHERE session_id = ?').all('session1');
|
|
const taskQuery = targetDb.prepare('SELECT * FROM episodes WHERE task = ?').all('task-0');
|
|
const agentQuery = targetDb.prepare('SELECT * FROM patterns WHERE agent_id = ?').all('agent-0');
|
|
|
|
targetDb.close();
|
|
|
|
expect(sessionQuery.length).toBeGreaterThan(0);
|
|
expect(taskQuery.length).toBeGreaterThan(0);
|
|
expect(agentQuery.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Query Performance', () => {
|
|
it('should perform indexed queries efficiently', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const start = performance.now();
|
|
|
|
// Indexed query
|
|
for (let i = 0; i < 100; i++) {
|
|
targetDb.prepare('SELECT * FROM episodes WHERE session_id = ?').all('session1');
|
|
}
|
|
|
|
const duration = performance.now() - start;
|
|
|
|
targetDb.close();
|
|
|
|
// 100 indexed queries should complete quickly
|
|
expect(duration).toBeLessThan(100); // <100ms for 100 queries
|
|
});
|
|
|
|
it('should handle complex joins efficiently', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const start = performance.now();
|
|
|
|
const results = targetDb.prepare(`
|
|
SELECT
|
|
e.id,
|
|
e.task,
|
|
p.pattern_type,
|
|
p.success_rate
|
|
FROM episodes e
|
|
LEFT JOIN patterns p ON e.task LIKE '%' || p.domain || '%'
|
|
WHERE e.success = 1
|
|
LIMIT 50
|
|
`).all();
|
|
|
|
const duration = performance.now() - start;
|
|
|
|
targetDb.close();
|
|
|
|
expect(results.length).toBeGreaterThan(0);
|
|
expect(duration).toBeLessThan(50); // <50ms for complex join
|
|
});
|
|
|
|
it('should aggregate large datasets efficiently', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const start = performance.now();
|
|
|
|
const stats = targetDb.prepare(`
|
|
SELECT
|
|
session_id,
|
|
COUNT(*) as episode_count,
|
|
AVG(reward) as avg_reward,
|
|
MAX(tokens_used) as max_tokens
|
|
FROM episodes
|
|
GROUP BY session_id
|
|
`).all();
|
|
|
|
const duration = performance.now() - start;
|
|
|
|
targetDb.close();
|
|
|
|
expect(stats.length).toBeGreaterThan(0);
|
|
expect(duration).toBeLessThan(20); // <20ms for aggregation
|
|
});
|
|
});
|
|
|
|
describe('Data Relationships', () => {
|
|
it('should maintain referential integrity concepts', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
// Get all agent IDs from patterns
|
|
const patternAgents = targetDb.prepare('SELECT DISTINCT agent_id FROM patterns').all() as Array<{ agent_id: string }>;
|
|
|
|
// Verify we can query related learning experiences
|
|
patternAgents.forEach(({ agent_id }) => {
|
|
if (agent_id) {
|
|
const query = targetDb.prepare(`
|
|
SELECT COUNT(*) as count FROM patterns WHERE agent_id = ?
|
|
`).get(agent_id) as { count: number };
|
|
|
|
expect(query.count).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
targetDb.close();
|
|
});
|
|
|
|
it('should support complex filtering', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const results = targetDb.prepare(`
|
|
SELECT * FROM patterns
|
|
WHERE success_rate > 0.8
|
|
AND usage_count > 10
|
|
AND domain IN ('domain-0', 'domain-1')
|
|
ORDER BY success_rate DESC
|
|
`).all();
|
|
|
|
targetDb.close();
|
|
|
|
results.forEach((row: any) => {
|
|
expect(row.success_rate).toBeGreaterThan(0.8);
|
|
expect(row.usage_count).toBeGreaterThan(10);
|
|
expect(['domain-0', 'domain-1']).toContain(row.domain);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle special characters in text fields', () => {
|
|
const db = new Database(targetDbPath);
|
|
|
|
const specialChars = "Test with 'quotes', \"double quotes\", and \n newlines";
|
|
|
|
db.prepare('INSERT INTO episodes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(
|
|
'special-test',
|
|
'session-special',
|
|
specialChars,
|
|
null,
|
|
null,
|
|
null,
|
|
0.5,
|
|
1,
|
|
1000,
|
|
500,
|
|
Date.now()
|
|
);
|
|
|
|
const result = db.prepare('SELECT task FROM episodes WHERE id = ?').get('special-test') as { task: string };
|
|
|
|
db.close();
|
|
|
|
expect(result.task).toBe(specialChars);
|
|
});
|
|
|
|
it('should handle empty strings vs NULL', () => {
|
|
const targetDb = new Database(targetDbPath, { readonly: true });
|
|
|
|
const hasNull = targetDb.prepare('SELECT * FROM episodes WHERE critique IS NULL LIMIT 1').get();
|
|
const hasEmpty = targetDb.prepare('SELECT * FROM episodes WHERE critique = "" LIMIT 1').get();
|
|
|
|
targetDb.close();
|
|
|
|
// Both should be handled correctly
|
|
expect(hasNull === undefined || hasNull !== null).toBe(true);
|
|
});
|
|
|
|
it('should handle very long text fields', () => {
|
|
const db = new Database(targetDbPath);
|
|
|
|
const longText = 'A'.repeat(10000);
|
|
|
|
db.prepare('INSERT INTO episodes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(
|
|
'long-test',
|
|
'session-long',
|
|
'task',
|
|
longText,
|
|
null,
|
|
null,
|
|
0.5,
|
|
1,
|
|
1000,
|
|
500,
|
|
Date.now()
|
|
);
|
|
|
|
const result = db.prepare('SELECT input FROM episodes WHERE id = ?').get('long-test') as { input: string };
|
|
|
|
db.close();
|
|
|
|
expect(result.input.length).toBe(10000);
|
|
});
|
|
});
|
|
});
|