godcrm/pes-core/test-integration.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

501 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// PES Integration Test — Full Lifecycle
// ============================================================
// Tests the COMPLETE cycle: birth → event → expression → react → learn
// Unlike unit tests, this exercises ALL subsystems together.
// ============================================================
import { Pes } from './core/pes.js';
import { unlinkSync, existsSync } from 'node:fs';
const DB_PATH = '/tmp/pes-integration-test.db';
const STATE_PATH = DB_PATH.replace(/\.db$/, '.state.json');
function cleanup() {
for (const f of [DB_PATH, STATE_PATH, DB_PATH + '-wal', DB_PATH + '-shm']) {
try { if (existsSync(f)) unlinkSync(f); } catch (_) {}
}
}
let passed = 0;
let failed = 0;
function assert(condition, msg) {
if (condition) {
passed++;
console.log(`${msg}`);
} else {
failed++;
console.log(` ❌ FAIL: ${msg}`);
}
}
// ── TEST 1: Full Lifecycle ──────────────────────────────────
console.log('\n═══ TEST 1: BIRTH → EVENTS → SLEEP → WAKE → DESTROY ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Тест',
ownerId: 'nikitron',
dbPath: DB_PATH,
breed: 'corgi',
seed: 0.42,
autoTick: false,
});
// Track events
const events = [];
pes.on('born', d => events.push({ type: 'born', data: d }));
pes.on('expression', d => events.push({ type: 'expression', data: d }));
pes.on('feedback', d => events.push({ type: 'feedback', data: d }));
pes.on('sleep', d => events.push({ type: 'sleep', data: d }));
pes.on('wake', d => events.push({ type: 'wake', data: d }));
pes.on('commandLearned', d => events.push({ type: 'commandLearned', data: d }));
pes.on('modeChange', d => events.push({ type: 'modeChange', data: d }));
// Birth
pes.birth();
assert(pes.isAlive, 'PES is alive after birth');
assert(pes.isAwake, 'PES is awake after birth');
assert(pes.name === 'Тест', 'PES has correct name');
assert(pes.breed === 'corgi', 'PES breed is corgi');
assert(events.some(e => e.type === 'born'), 'born event emitted');
assert(pes.emotions !== null, 'Emotions subsystem initialized');
assert(pes.store !== null, 'Store subsystem initialized');
assert(pes.engine !== null, 'Expression engine initialized');
// Corgi trait bias
assert(pes.emotions.traits.playfulness > 0.3, 'Corgi has elevated playfulness');
assert(pes.emotions.traits.stubbornness > 0.3, 'Corgi has elevated stubbornness');
// Event: owner returned
const greetResult = pes.event({ type: 'owner_returned', from: 'owner' });
assert(greetResult !== null, 'owner_returned produces expression');
assert(events.some(e => e.type === 'expression'), 'expression event emitted');
// Event: bug detected → should give XP
const xpBefore = pes.xp;
const bugResult = pes.event({ type: 'bug_detected', from: 'system', file: 'test.js', line: 42 });
assert(bugResult !== null, 'bug_detected produces expression');
assert(pes.xp >= xpBefore, 'XP increased or stayed (bug_detected gives XP)');
// Praise → mood should improve
const moodBefore = pes.mood;
pes.praise('хороший мальчик');
// Mood may or may not increase immediately depending on state transitions
assert(pes.mood >= 0, 'Mood is valid after praise');
// Scold
pes.scold('плохо');
assert(pes.mood >= 0 && pes.mood <= 1, 'Mood is valid after scold');
// Feed
const feedResult = pes.feed({ type: 'good_data', content: 'test data' });
assert(feedResult !== null || feedResult === null, 'Feed returns result (may be null if suppressed)');
// Status
const status = pes.status();
assert(status.alive === true, 'Status shows alive');
assert(status.name === 'Тест', 'Status shows correct name');
assert(typeof status.level === 'number', 'Status has level');
assert(typeof status.emotion === 'string', 'Status has emotion');
assert(typeof status.mood === 'number', 'Status has mood');
assert(typeof status.energy === 'number', 'Status has energy');
// Inspect
const inspected = pes.inspect();
assert(inspected.traits !== undefined, 'Inspect has traits');
assert(inspected.stateHistory !== undefined, 'Inspect has state history');
// Sleep
pes.sleep();
assert(pes.isAwake === false, 'PES is not awake after sleep');
assert(pes.isAlive, 'PES is still alive after sleep');
// Wake
pes.wake();
assert(pes.isAwake, 'PES is awake after wake');
assert(events.some(e => e.type === 'wake'), 'wake event emitted');
// Destroy
pes.destroy();
assert(pes.isAlive === false || pes._destroyed, 'PES is destroyed');
}
// ── TEST 2: Command Learning Cycle ──────────────────────────
console.log('\n═══ TEST 2: COMMAND LEARNING — UNKNOWN → REPEAT → LEARN ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Ученик',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.5,
autoTick: false,
});
pes.birth();
let commandLearned = false;
pes.on('commandLearned', () => { commandLearned = true; });
// Unknown command — attempt 1
const r1 = pes.command('scan_api');
assert(r1.executed === false, 'Unknown command not executed');
assert(r1.reason === 'unknown', 'Reason is "unknown"');
assert(r1.attempts === 1, 'First attempt recorded');
// Attempt 2
const r2 = pes.command('scan_api');
assert(r2.executed === false, 'Still unknown at attempt 2');
assert(r2.attempts === 2, 'Second attempt');
// Attempt 3 — should learn!
const r3 = pes.command('scan_api');
assert(r3.commandLearned === true, 'Command learned on 3rd attempt');
assert(commandLearned, 'commandLearned event emitted');
// Now execute the learned command
const r4 = pes.command('scan_api');
assert(r4.executed === true, 'Learned command executes');
// Verify in store
const cmd = pes.store.getCommand('scan_api');
assert(cmd !== null, 'Command exists in store');
assert(cmd.understood === 1, 'Command is marked as understood');
assert(cmd.willingness >= 0.3, 'Command has willingness');
pes.destroy();
}
// ── TEST 3: Feedback Loop ───────────────────────────────────
console.log('\n═══ TEST 3: FEEDBACK LOOP — EXPRESSION → REACTION → LEARN ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Фидбэк',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.3,
autoTick: false,
});
pes.birth();
const expressions = [];
pes.on('expression', (expr) => expressions.push(expr));
// Generate an expression
const result = pes.event({ type: 'bug_detected', from: 'system', file: 'app.js', line: 10 });
if (result && !result.suppressed && result.id) {
// React positively
const feedback = pes.react(result.id, {
from: 'owner',
type: 'emoji',
value: '👍',
speed_ms: 2000,
});
assert(feedback !== null, 'Feedback returned (reaction processed)');
if (feedback) {
assert(feedback.positive === true, 'Reaction classified as positive');
assert(feedback.weight > 0, 'Positive weight calculated');
}
// React negatively to next expression
const result2 = pes.event({ type: 'owner_returned', from: 'owner' });
if (result2 && !result2.suppressed && result2.id) {
const feedback2 = pes.react(result2.id, {
from: 'owner',
type: 'emoji',
value: '👎',
speed_ms: 5000,
});
if (feedback2) {
assert(feedback2.positive === false, 'Negative reaction classified correctly');
assert(feedback2.weight < 0, 'Negative weight calculated');
} else {
assert(true, 'Negative reaction processed (null ok if pending expired)');
}
} else {
assert(true, 'Second expression skipped (cooldown/suppressed)');
}
} else {
assert(true, 'Expression suppressed — feedback loop skipped (valid behavior)');
}
// Check reaction memory has entries
const history = pes.store.getHistory({ limit: 10 });
assert(history.length > 0, 'Interaction history recorded');
pes.destroy();
}
// ── TEST 4: Passive Tick & Auto-mode ────────────────────────
console.log('\n═══ TEST 4: PASSIVE TICK & AUTO-MODE ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Тикер',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.6,
autoTick: false,
});
pes.birth();
const modeChanges = [];
pes.on('modeChange', (d) => modeChanges.push(d));
// Simulate time passing — manually call _tick
assert(pes.mode === 'active', 'Starts in active mode');
// Simulate 10 minutes of silence
pes._lastActivityAt = Date.now() - 10 * 60_000;
pes._tick();
assert(pes.mode === 'idle', 'Switches to idle after 10 min silence');
// Simulate 45 minutes of silence
pes._lastActivityAt = Date.now() - 45 * 60_000;
pes._tick();
assert(pes.mode === 'sleeping', 'Switches to sleeping after 45 min silence');
// Simulate 3 hours of silence
pes._lastActivityAt = Date.now() - 180 * 60_000;
pes._tick();
assert(pes.mode === 'away', 'Switches to away after 3 hours silence');
// Activity resets to active
pes.event({ type: 'owner_returned', from: 'owner' });
assert(pes.mode === 'active', 'Returns to active on event');
pes.destroy();
}
// ── TEST 5: Export / Import ─────────────────────────────────
console.log('\n═══ TEST 5: EXPORT → IMPORT ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Экспорт',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.7,
domain: 'devops',
autoTick: false,
});
pes.birth();
// Build up some state
pes.event({ type: 'owner_returned', from: 'owner' });
pes.praise('хороший');
pes.command('sit');
pes.command('sit');
pes.command('sit'); // learn it
// Export
const dump = pes.export();
assert(dump.version === 1, 'Export has version');
assert(dump.identity.name === 'Экспорт', 'Export has name');
assert(dump.identity.breed === 'corgi', 'Export has breed');
assert(dump.emotions !== null, 'Export has emotions');
assert(dump.snapshot !== null, 'Export has snapshot');
pes.destroy();
// Import into new DB
const DB2 = '/tmp/pes-integration-import.db';
try { unlinkSync(DB2); } catch (_) {}
try { unlinkSync(DB2.replace('.db', '.state.json')); } catch (_) {}
const pes2 = Pes.import(dump, DB2);
assert(pes2.name === 'Экспорт', 'Imported PES has correct name');
assert(pes2.breed === 'corgi', 'Imported PES has correct breed');
assert(pes2.birthday === dump.identity.birthday, 'Imported PES has correct birthday');
assert(pes2.isAlive, 'Imported PES is alive');
assert(!pes2.isAwake, 'Imported PES needs wake()');
// Wake and verify
pes2.wake();
assert(pes2.isAwake, 'Imported PES wakes up');
assert(pes2.emotions !== null, 'Imported PES has emotions');
pes2.destroy();
try { unlinkSync(DB2); } catch (_) {}
try { unlinkSync(DB2.replace('.db', '.state.json')); } catch (_) {}
try { unlinkSync(DB2 + '-wal'); } catch (_) {}
try { unlinkSync(DB2 + '-shm'); } catch (_) {}
}
// ── TEST 6: Runaway (Irreversible) ──────────────────────────
console.log('\n═══ TEST 6: RUNAWAY — FAREWELL LETTER ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Беглец',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.1,
autoTick: false,
});
pes.birth();
let runawayEvent = null;
pes.on('runaway', (d) => { runawayEvent = d; });
// Build some history
pes.event({ type: 'owner_returned', from: 'owner' });
pes.praise('test');
// Runaway
const letter = pes.runaway('dual_rage', 'злой_pes');
assert(letter !== null, 'Farewell letter generated');
assert(letter.type === 'farewell', 'Letter type is farewell');
assert(letter.reason === 'dual_rage', 'Letter has reason');
assert(letter.snapshot !== null, 'Letter has snapshot');
assert(letter.finalWords !== undefined, 'Letter has final words');
assert(runawayEvent !== null, 'runaway event emitted');
assert(pes.isAlive === false, 'PES is not alive after runaway');
// Cannot interact anymore
const result = pes.event({ type: 'owner_returned', from: 'owner' });
assert(result === null, 'Cannot interact with dead PES');
pes.destroy();
}
// ── TEST 7: Silent Mode ─────────────────────────────────────
console.log('\n═══ TEST 7: SILENT MODE ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Тихий',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.9,
autoTick: false,
});
pes.birth();
// Enable silent mode
pes.setSilentMode(true);
assert(pes.engine.silentMode === true, 'Silent mode enabled');
// Expression should have no voice
const result = pes.event({ type: 'bug_detected', from: 'system' });
if (result && result.expression) {
assert(result.expression.voiceKey === null || result.expression.voiceKey === undefined,
'Voice is suppressed in silent mode');
} else {
assert(true, 'Expression suppressed (valid)');
}
// Disable
pes.setSilentMode(false);
assert(pes.engine.silentMode === false, 'Silent mode disabled');
pes.destroy();
}
// ── TEST 8: Domain Context ──────────────────────────────────
console.log('\n═══ TEST 8: DOMAIN CONTEXT ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Домен',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.4,
domain: 'fintech',
autoTick: false,
});
pes.birth();
assert(pes.domain === 'fintech', 'Domain set on creation');
// Change domain
pes.setDomain('devops');
assert(pes.domain === 'devops', 'Domain changed');
const status = pes.status();
assert(status.alive, 'PES alive with domain');
pes.destroy();
}
// ── TEST 9: Multiple Events → Store Persistence ─────────────
console.log('\n═══ TEST 9: PERSISTENCE — INTERACTIONS RECORDED ═══\n');
cleanup();
{
const pes = new Pes({
name: 'Память',
ownerId: 'nikitron',
dbPath: DB_PATH,
seed: 0.2,
autoTick: false,
});
pes.birth();
// Generate multiple events
pes.event({ type: 'owner_returned', from: 'owner' });
pes.event({ type: 'bug_detected', from: 'system' });
pes.praise('отлично');
pes.scold('нет');
pes.feed({ type: 'data' });
// Check interactions are in store
const history = pes.store.getHistory({ limit: 50 });
assert(history.length >= 3, `Multiple interactions recorded (got ${history.length})`);
// Check stats
const stats = pes.store.getStats();
assert(stats !== null, 'Stats exist');
assert(stats.xp >= 0, 'XP is tracked');
// Save and reload
pes.save();
assert(existsSync(STATE_PATH), 'State file saved');
// Create new PES from same DB
pes.destroy();
const pes2 = new Pes({
name: 'Память',
ownerId: 'nikitron',
dbPath: DB_PATH,
autoTick: false,
});
pes2.wake();
const history2 = pes2.store.getHistory({ limit: 50 });
assert(history2.length >= 3, `History survives restart (got ${history2.length})`);
pes2.destroy();
}
// ── SUMMARY ─────────────────────────────────────────────────
cleanup();
console.log('\n═══════════════════════════════════════════════════════');
console.log(` INTEGRATION TESTS: ${passed} passed, ${failed} failed`);
console.log('═══════════════════════════════════════════════════════\n');
if (failed > 0) process.exit(1);