589 lines
18 KiB
JavaScript
589 lines
18 KiB
JavaScript
/**
|
|
* Edge Case Tests
|
|
* Tests empty states, maximum capacity, rapid transitions, malformed data, and boundary conditions
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const crypto = require('crypto');
|
|
const { createMockLearning } = require('./learning-lifecycle.test.cjs');
|
|
const { createMockRAC } = require('./rac-coherence.test.cjs');
|
|
|
|
/**
|
|
* Test 1: Empty State Handling
|
|
*/
|
|
function testEmptyStates() {
|
|
console.log('\n=== Test 1: Empty State Handling ===');
|
|
|
|
const learningWasm = createMockLearning();
|
|
const racWasm = createMockRAC();
|
|
|
|
const learning = new learningWasm.NetworkLearning();
|
|
const coherence = new racWasm.CoherenceEngine();
|
|
|
|
// Empty learning operations
|
|
assert.strictEqual(learning.trajectoryCount(), 0);
|
|
assert.strictEqual(learning.patternCount(), 0);
|
|
console.log('✓ Empty learning state initialized');
|
|
|
|
const emptyStats = JSON.parse(learning.getStats());
|
|
assert.strictEqual(emptyStats.trajectories.total, 0);
|
|
assert.strictEqual(emptyStats.reasoning_bank.total_patterns, 0);
|
|
console.log('✓ Empty stats handled correctly');
|
|
|
|
// Empty lookups
|
|
const emptyResults = JSON.parse(learning.lookupPatterns(JSON.stringify([1, 0, 0]), 5));
|
|
assert.strictEqual(emptyResults.length, 0);
|
|
console.log('✓ Empty pattern lookup returns empty array');
|
|
|
|
// Empty RAC operations
|
|
assert.strictEqual(coherence.eventCount(), 0);
|
|
assert.strictEqual(coherence.conflictCount(), 0);
|
|
assert.strictEqual(coherence.quarantinedCount(), 0);
|
|
console.log('✓ Empty RAC state initialized');
|
|
|
|
// Empty Merkle root
|
|
const emptyRoot = coherence.getMerkleRoot();
|
|
assert.strictEqual(emptyRoot.length, 64); // Hex string of 32 bytes
|
|
console.log('✓ Empty Merkle root generated');
|
|
|
|
// Can use any claim in empty state
|
|
assert.ok(coherence.canUseClaim('nonexistent-claim'));
|
|
console.log('✓ Nonexistent claims are usable by default');
|
|
|
|
console.log('✅ Empty State Handling Test PASSED');
|
|
return {
|
|
learning_empty: true,
|
|
rac_empty: true,
|
|
handles_empty_lookups: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 2: Maximum Capacity Scenarios
|
|
*/
|
|
function testMaxCapacity() {
|
|
console.log('\n=== Test 2: Maximum Capacity Scenarios ===');
|
|
|
|
const learningWasm = createMockLearning();
|
|
const racWasm = createMockRAC();
|
|
|
|
// Test trajectory ring buffer wraparound
|
|
const tracker = new learningWasm.TrajectoryTracker(100); // Small buffer
|
|
|
|
for (let i = 0; i < 250; i++) {
|
|
const success = tracker.record(JSON.stringify({
|
|
task_vector: [i, i, i],
|
|
latency_ms: 50,
|
|
energy_spent: 50,
|
|
energy_earned: 100,
|
|
success: true,
|
|
executor_id: `node-${i}`,
|
|
timestamp: Date.now() + i
|
|
}));
|
|
assert.ok(success, `Failed to record trajectory ${i}`);
|
|
}
|
|
|
|
assert.strictEqual(tracker.count(), 100, 'Trajectory buffer should cap at max size');
|
|
console.log('✓ Trajectory ring buffer wraps correctly (100/250 retained)');
|
|
|
|
// Test pattern storage at scale
|
|
const bank = new learningWasm.ReasoningBank();
|
|
const patternCount = 10000;
|
|
|
|
for (let i = 0; i < patternCount; i++) {
|
|
const id = bank.store(JSON.stringify({
|
|
centroid: [Math.random(), Math.random(), Math.random()],
|
|
optimal_allocation: 0.8,
|
|
optimal_energy: 100,
|
|
confidence: 0.7 + Math.random() * 0.3,
|
|
sample_count: 5,
|
|
avg_latency_ms: 50,
|
|
avg_success_rate: 0.9
|
|
}));
|
|
assert.ok(id >= 0, `Failed to store pattern ${i}`);
|
|
}
|
|
|
|
assert.strictEqual(bank.count(), patternCount);
|
|
console.log(`✓ Stored ${patternCount} patterns successfully`);
|
|
|
|
// Test RAC event log at scale
|
|
const coherence = new racWasm.CoherenceEngine();
|
|
const eventCount = 10000;
|
|
|
|
for (let i = 0; i < eventCount; i++) {
|
|
coherence.ingest({
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Date.now() + i,
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(crypto.randomBytes(32)),
|
|
ruvector: { dims: [0, 0, 0] },
|
|
kind: {
|
|
Assert: {
|
|
proposition: Buffer.from(`claim-${i}`),
|
|
evidence: [],
|
|
confidence: 0.8,
|
|
expires_at_unix_ms: null
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
});
|
|
}
|
|
|
|
assert.strictEqual(coherence.eventCount(), eventCount);
|
|
console.log(`✓ Ingested ${eventCount} RAC events successfully`);
|
|
|
|
console.log('✅ Maximum Capacity Test PASSED');
|
|
return {
|
|
trajectory_buffer_size: tracker.count(),
|
|
pattern_count: bank.count(),
|
|
event_count: coherence.eventCount()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 3: Rapid State Transitions
|
|
*/
|
|
function testRapidTransitions() {
|
|
console.log('\n=== Test 3: Rapid State Transitions ===');
|
|
|
|
const racWasm = createMockRAC();
|
|
const coherence = new racWasm.CoherenceEngine();
|
|
|
|
const context = crypto.randomBytes(32);
|
|
const claim = {
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Date.now(),
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(context),
|
|
ruvector: { dims: [0, 0, 0] },
|
|
kind: {
|
|
Assert: {
|
|
proposition: Buffer.from('rapid-transition-claim'),
|
|
evidence: [],
|
|
confidence: 0.8,
|
|
expires_at_unix_ms: null
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
};
|
|
|
|
coherence.ingest(claim);
|
|
const claimHex = Buffer.from(claim.id).toString('hex');
|
|
|
|
// Rapid transitions: None → Challenge → Resolution → Deprecate
|
|
assert.strictEqual(coherence.getQuarantineLevel(claimHex), 0);
|
|
console.log('✓ State 1: None (level 0)');
|
|
|
|
// Challenge (level 2)
|
|
const challenge = {
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Date.now() + 1,
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(context),
|
|
ruvector: { dims: [0, 0, 0] },
|
|
kind: {
|
|
Challenge: {
|
|
conflict_id: Array.from(crypto.randomBytes(32)),
|
|
claim_ids: [claim.id],
|
|
reason: 'Rapid test',
|
|
requested_proofs: []
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
};
|
|
|
|
coherence.ingest(challenge);
|
|
assert.strictEqual(coherence.getQuarantineLevel(claimHex), 2);
|
|
console.log('✓ State 2: Challenged (level 2)');
|
|
|
|
// Resolution accepting claim (level 0)
|
|
const resolution = {
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Date.now() + 2,
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(context),
|
|
ruvector: { dims: [0, 0, 0] },
|
|
kind: {
|
|
Resolution: {
|
|
conflict_id: challenge.kind.Challenge.conflict_id,
|
|
accepted: [claim.id],
|
|
deprecated: [],
|
|
rationale: [],
|
|
authority_sigs: []
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
};
|
|
|
|
coherence.ingest(resolution);
|
|
assert.strictEqual(coherence.getQuarantineLevel(claimHex), 0);
|
|
console.log('✓ State 3: Resolved/Accepted (level 0)');
|
|
|
|
// Deprecation (level 3)
|
|
const deprecate = {
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Date.now() + 3,
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(context),
|
|
ruvector: { dims: [0, 0, 0] },
|
|
kind: {
|
|
Deprecate: {
|
|
claim_id: claim.id,
|
|
by_resolution: Array.from(crypto.randomBytes(32)),
|
|
superseded_by: null
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
};
|
|
|
|
coherence.ingest(deprecate);
|
|
assert.strictEqual(coherence.getQuarantineLevel(claimHex), 3);
|
|
console.log('✓ State 4: Deprecated (level 3)');
|
|
|
|
// All transitions within milliseconds
|
|
console.log('✓ Rapid transitions (0 → 2 → 0 → 3) handled correctly');
|
|
|
|
console.log('✅ Rapid State Transitions Test PASSED');
|
|
return {
|
|
transitions: 4,
|
|
final_state: 'deprecated',
|
|
final_level: 3
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 4: Malformed Data Handling
|
|
*/
|
|
function testMalformedData() {
|
|
console.log('\n=== Test 4: Malformed Data Handling ===');
|
|
|
|
const learningWasm = createMockLearning();
|
|
const learning = new learningWasm.NetworkLearning();
|
|
|
|
// Invalid JSON
|
|
const invalidJson = learning.storePattern('not valid json');
|
|
assert.strictEqual(invalidJson, -1);
|
|
console.log('✓ Invalid JSON rejected (returns -1)');
|
|
|
|
// Missing required fields
|
|
const invalidPattern = learning.storePattern(JSON.stringify({
|
|
centroid: [1, 0, 0]
|
|
// Missing other required fields
|
|
}));
|
|
assert.strictEqual(invalidPattern, -1);
|
|
console.log('✓ Incomplete pattern rejected');
|
|
|
|
// Wrong data types
|
|
const wrongTypes = learning.recordTrajectory(JSON.stringify({
|
|
task_vector: "not an array",
|
|
latency_ms: "not a number",
|
|
energy_spent: null,
|
|
energy_earned: undefined,
|
|
success: "not a boolean",
|
|
executor_id: 12345,
|
|
timestamp: "not a number"
|
|
}));
|
|
// Mock should handle this gracefully
|
|
console.log('✓ Wrong data types handled gracefully');
|
|
|
|
// Empty vectors
|
|
const emptyVector = learning.lookupPatterns(JSON.stringify([]), 5);
|
|
assert.strictEqual(emptyVector, '[]');
|
|
console.log('✓ Empty vector query returns empty results');
|
|
|
|
// Negative values
|
|
const bank = new learningWasm.ReasoningBank();
|
|
bank.store(JSON.stringify({
|
|
centroid: [1, 0, 0],
|
|
optimal_allocation: -0.5, // Invalid
|
|
optimal_energy: -100, // Invalid
|
|
confidence: 1.5, // Out of range
|
|
sample_count: -10, // Invalid
|
|
avg_latency_ms: -50, // Invalid
|
|
avg_success_rate: 2.0 // Out of range
|
|
}));
|
|
// Should store but may have clamped values
|
|
console.log('✓ Out-of-range values accepted (implementation may clamp)');
|
|
|
|
// Null/undefined handling
|
|
const nullTrajectory = learning.recordTrajectory(null);
|
|
assert.strictEqual(nullTrajectory, false);
|
|
console.log('✓ Null trajectory rejected');
|
|
|
|
const undefinedPattern = learning.storePattern(undefined);
|
|
assert.strictEqual(undefinedPattern, -1);
|
|
console.log('✓ Undefined pattern rejected');
|
|
|
|
console.log('✅ Malformed Data Handling Test PASSED');
|
|
return {
|
|
invalid_json_rejected: true,
|
|
null_handling: true,
|
|
type_safety: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 5: Boundary Conditions
|
|
*/
|
|
function testBoundaryConditions() {
|
|
console.log('\n=== Test 5: Boundary Conditions ===');
|
|
|
|
const learningWasm = createMockLearning();
|
|
const racWasm = createMockRAC();
|
|
|
|
// Zero-dimensional vectors
|
|
const learning = new learningWasm.NetworkLearning();
|
|
const zeroVecPattern = learning.storePattern(JSON.stringify({
|
|
centroid: [],
|
|
optimal_allocation: 0.8,
|
|
optimal_energy: 100,
|
|
confidence: 0.9,
|
|
sample_count: 10,
|
|
avg_latency_ms: 50,
|
|
avg_success_rate: 0.95
|
|
}));
|
|
assert.ok(zeroVecPattern >= 0);
|
|
console.log('✓ Zero-dimensional vector stored');
|
|
|
|
// Very high-dimensional vectors
|
|
const highDimVec = Array(10000).fill(0).map(() => Math.random());
|
|
const highDimPattern = learning.storePattern(JSON.stringify({
|
|
centroid: highDimVec,
|
|
optimal_allocation: 0.8,
|
|
optimal_energy: 100,
|
|
confidence: 0.9,
|
|
sample_count: 10,
|
|
avg_latency_ms: 50,
|
|
avg_success_rate: 0.95
|
|
}));
|
|
assert.ok(highDimPattern >= 0);
|
|
console.log('✓ 10,000-dimensional vector stored');
|
|
|
|
// Zero confidence/energy
|
|
const zeroConfidence = learning.storePattern(JSON.stringify({
|
|
centroid: [1, 0, 0],
|
|
optimal_allocation: 0.0,
|
|
optimal_energy: 0,
|
|
confidence: 0.0,
|
|
sample_count: 0,
|
|
avg_latency_ms: 0,
|
|
avg_success_rate: 0.0
|
|
}));
|
|
assert.ok(zeroConfidence >= 0);
|
|
console.log('✓ Zero confidence/energy pattern stored');
|
|
|
|
// Maximum values
|
|
const maxValues = learning.storePattern(JSON.stringify({
|
|
centroid: Array(100).fill(Number.MAX_VALUE),
|
|
optimal_allocation: 1.0,
|
|
optimal_energy: Number.MAX_SAFE_INTEGER,
|
|
confidence: 1.0,
|
|
sample_count: Number.MAX_SAFE_INTEGER,
|
|
avg_latency_ms: Number.MAX_VALUE,
|
|
avg_success_rate: 1.0
|
|
}));
|
|
assert.ok(maxValues >= 0);
|
|
console.log('✓ Maximum values stored');
|
|
|
|
// Spike attention edge cases
|
|
const spike = new learningWasm.SpikeDrivenAttention();
|
|
|
|
const zeroRatio = spike.energyRatio(0, 0);
|
|
assert.strictEqual(zeroRatio, 1.0);
|
|
console.log('✓ Zero-length sequences return 1.0 energy ratio');
|
|
|
|
const singleRatio = spike.energyRatio(1, 1);
|
|
assert.ok(singleRatio > 0);
|
|
console.log('✓ Single-element sequences handled');
|
|
|
|
const largeRatio = spike.energyRatio(10000, 10000);
|
|
assert.ok(largeRatio > 1.0 && largeRatio < 1000);
|
|
console.log('✓ Very large sequences bounded');
|
|
|
|
// Multi-head attention boundaries
|
|
const minAttn = new learningWasm.MultiHeadAttention(2, 1);
|
|
assert.strictEqual(minAttn.dim(), 2);
|
|
assert.strictEqual(minAttn.numHeads(), 1);
|
|
console.log('✓ Minimum attention configuration (2 dim, 1 head)');
|
|
|
|
const maxAttn = new learningWasm.MultiHeadAttention(1024, 64);
|
|
assert.strictEqual(maxAttn.dim(), 1024);
|
|
assert.strictEqual(maxAttn.numHeads(), 64);
|
|
console.log('✓ Large attention configuration (1024 dim, 64 heads)');
|
|
|
|
// RAC event boundaries
|
|
const coherence = new racWasm.CoherenceEngine();
|
|
|
|
// Minimal event
|
|
const minEvent = {
|
|
id: Array.from(Buffer.alloc(32)),
|
|
prev: null,
|
|
ts_unix_ms: 0,
|
|
author: Array.from(Buffer.alloc(32)),
|
|
context: Array.from(Buffer.alloc(32)),
|
|
ruvector: { dims: [] },
|
|
kind: {
|
|
Assert: {
|
|
proposition: Buffer.from(''),
|
|
evidence: [],
|
|
confidence: 0,
|
|
expires_at_unix_ms: null
|
|
}
|
|
},
|
|
sig: Array.from(Buffer.alloc(64))
|
|
};
|
|
|
|
coherence.ingest(minEvent);
|
|
assert.strictEqual(coherence.eventCount(), 1);
|
|
console.log('✓ Minimal event ingested');
|
|
|
|
// Maximum timestamp
|
|
const maxTimestamp = {
|
|
id: Array.from(crypto.randomBytes(32)),
|
|
prev: null,
|
|
ts_unix_ms: Number.MAX_SAFE_INTEGER,
|
|
author: Array.from(crypto.randomBytes(32)),
|
|
context: Array.from(crypto.randomBytes(32)),
|
|
ruvector: { dims: [0] },
|
|
kind: {
|
|
Assert: {
|
|
proposition: Buffer.from('max-timestamp'),
|
|
evidence: [],
|
|
confidence: 0.8,
|
|
expires_at_unix_ms: Number.MAX_SAFE_INTEGER
|
|
}
|
|
},
|
|
sig: Array.from(crypto.randomBytes(64))
|
|
};
|
|
|
|
coherence.ingest(maxTimestamp);
|
|
assert.strictEqual(coherence.eventCount(), 2);
|
|
console.log('✓ Maximum timestamp handled');
|
|
|
|
console.log('✅ Boundary Conditions Test PASSED');
|
|
return {
|
|
zero_dim_vectors: true,
|
|
high_dim_vectors: true,
|
|
extreme_values: true,
|
|
minimal_events: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 6: Concurrent Modification Safety
|
|
*/
|
|
function testConcurrentModificationSafety() {
|
|
console.log('\n=== Test 6: Concurrent Modification Safety ===');
|
|
|
|
const learningWasm = createMockLearning();
|
|
const learning = new learningWasm.NetworkLearning();
|
|
|
|
// Interleaved reads and writes
|
|
const operations = 100;
|
|
|
|
for (let i = 0; i < operations; i++) {
|
|
// Write
|
|
learning.storePattern(JSON.stringify({
|
|
centroid: [i, i, i],
|
|
optimal_allocation: 0.8,
|
|
optimal_energy: 100,
|
|
confidence: 0.9,
|
|
sample_count: 10,
|
|
avg_latency_ms: 50,
|
|
avg_success_rate: 0.95
|
|
}));
|
|
|
|
// Read
|
|
if (i > 0) {
|
|
const results = JSON.parse(learning.lookupPatterns(JSON.stringify([i, i, i]), 5));
|
|
assert.ok(results.length >= 0);
|
|
}
|
|
|
|
// Modify (prune)
|
|
if (i % 10 === 0 && i > 0) {
|
|
learning.prune(100, 0.5);
|
|
}
|
|
|
|
// Read stats
|
|
const stats = JSON.parse(learning.getStats());
|
|
assert.ok(stats.reasoning_bank.total_patterns >= 0);
|
|
}
|
|
|
|
console.log(`✓ Completed ${operations} interleaved operations`);
|
|
console.log('✓ No concurrent modification errors');
|
|
|
|
console.log('✅ Concurrent Modification Safety Test PASSED');
|
|
return {
|
|
operations: operations,
|
|
safe: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Run all edge case tests
|
|
*/
|
|
function runEdgeCaseTests() {
|
|
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ Edge Case Simulation Tests ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝');
|
|
|
|
const results = {
|
|
timestamp: new Date().toISOString(),
|
|
test_suite: 'edge_cases',
|
|
tests: {}
|
|
};
|
|
|
|
try {
|
|
results.tests.empty_states = testEmptyStates();
|
|
results.tests.max_capacity = testMaxCapacity();
|
|
results.tests.rapid_transitions = testRapidTransitions();
|
|
results.tests.malformed_data = testMalformedData();
|
|
results.tests.boundary_conditions = testBoundaryConditions();
|
|
results.tests.concurrent_safety = testConcurrentModificationSafety();
|
|
|
|
results.summary = {
|
|
total_tests: 6,
|
|
passed: 6,
|
|
failed: 0,
|
|
success_rate: 1.0
|
|
};
|
|
|
|
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ All Edge Case Tests PASSED ✅ ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝\n');
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Test failed:', error.message);
|
|
console.error(error.stack);
|
|
results.summary = { total_tests: 6, passed: 0, failed: 1, error: error.message };
|
|
process.exit(1);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
const results = runEdgeCaseTests();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const reportsDir = path.join(__dirname, '../reports');
|
|
if (!fs.existsSync(reportsDir)) {
|
|
fs.mkdirSync(reportsDir, { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(
|
|
path.join(reportsDir, 'edge-cases-results.json'),
|
|
JSON.stringify(results, null, 2)
|
|
);
|
|
console.log('📊 Results saved to: sim/reports/edge-cases-results.json');
|
|
}
|
|
|
|
module.exports = { runEdgeCaseTests };
|