421 lines
12 KiB
JavaScript
421 lines
12 KiB
JavaScript
import { test, describe } from 'node:test';
|
|
import { strictEqual, ok, deepStrictEqual } from 'node:assert';
|
|
|
|
/**
|
|
* Basic Test Suite for Psycho-Symbolic Reasoner
|
|
*
|
|
* These tests verify core functionality during development.
|
|
* More comprehensive tests will be added as implementation progresses.
|
|
*/
|
|
|
|
describe('Psycho-Symbolic Reasoner Tests', () => {
|
|
|
|
test('package.json is valid', async () => {
|
|
const pkg = await import('../package.json', { assert: { type: 'json' } });
|
|
|
|
strictEqual(pkg.default.name, 'psycho-symbolic-reasoner');
|
|
strictEqual(pkg.default.version, '1.0.0');
|
|
ok(pkg.default.description);
|
|
ok(pkg.default.keywords.length > 0);
|
|
strictEqual(pkg.default.license, 'MIT');
|
|
});
|
|
|
|
test('example files exist and are executable', async () => {
|
|
const fs = await import('fs/promises');
|
|
const path = await import('path');
|
|
|
|
const examplesDir = path.resolve('./examples');
|
|
const files = await fs.readdir(examplesDir);
|
|
|
|
ok(files.includes('basic-usage.js'));
|
|
ok(files.includes('mcp-integration.js'));
|
|
ok(files.includes('knowledge-base.json'));
|
|
|
|
// Check if example files are readable
|
|
const basicUsage = await fs.readFile(path.join(examplesDir, 'basic-usage.js'), 'utf8');
|
|
ok(basicUsage.includes('PsychoSymbolicReasoner'));
|
|
});
|
|
|
|
test('knowledge base structure is valid', async () => {
|
|
const knowledgeBase = await import('../examples/knowledge-base.json', {
|
|
assert: { type: 'json' }
|
|
});
|
|
|
|
const kb = knowledgeBase.default;
|
|
|
|
ok(kb.metadata);
|
|
ok(Array.isArray(kb.nodes));
|
|
ok(Array.isArray(kb.edges));
|
|
ok(Array.isArray(kb.rules));
|
|
|
|
// Check that nodes have required fields
|
|
if (kb.nodes.length > 0) {
|
|
const firstNode = kb.nodes[0];
|
|
ok(firstNode.id);
|
|
ok(firstNode.type);
|
|
ok(firstNode.properties);
|
|
}
|
|
|
|
// Check that edges reference valid nodes
|
|
if (kb.edges.length > 0) {
|
|
const firstEdge = kb.edges[0];
|
|
ok(firstEdge.from);
|
|
ok(firstEdge.to);
|
|
ok(firstEdge.relationship);
|
|
}
|
|
});
|
|
|
|
test('TypeScript configuration is valid', async () => {
|
|
const fs = await import('fs/promises');
|
|
|
|
const tsconfigPath = './tsconfig.json';
|
|
const tsconfig = JSON.parse(await fs.readFile(tsconfigPath, 'utf8'));
|
|
|
|
ok(tsconfig.compilerOptions);
|
|
strictEqual(tsconfig.compilerOptions.target, 'ES2022');
|
|
strictEqual(tsconfig.compilerOptions.module, 'ESNext');
|
|
ok(Array.isArray(tsconfig.include));
|
|
ok(Array.isArray(tsconfig.exclude));
|
|
});
|
|
|
|
test('CI/CD configuration exists', async () => {
|
|
const fs = await import('fs/promises');
|
|
const path = await import('path');
|
|
|
|
const ciPath = path.join('.github', 'workflows', 'ci.yml');
|
|
const ciConfig = await fs.readFile(ciPath, 'utf8');
|
|
|
|
ok(ciConfig.includes('CI/CD Pipeline'));
|
|
ok(ciConfig.includes('test-rust'));
|
|
ok(ciConfig.includes('test-typescript'));
|
|
ok(ciConfig.includes('publish'));
|
|
});
|
|
|
|
test('documentation files exist', async () => {
|
|
const fs = await import('fs/promises');
|
|
|
|
const docs = [
|
|
'README.md',
|
|
'CHANGELOG.md',
|
|
'CONTRIBUTING.md',
|
|
'LICENSE',
|
|
'docs/API.md'
|
|
];
|
|
|
|
for (const doc of docs) {
|
|
try {
|
|
await fs.access(doc);
|
|
ok(true, `${doc} exists`);
|
|
} catch (error) {
|
|
ok(false, `${doc} is missing`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Rust workspace configuration', async () => {
|
|
const fs = await import('fs/promises');
|
|
|
|
const cargoToml = await fs.readFile('./Cargo.toml', 'utf8');
|
|
|
|
ok(cargoToml.includes('[workspace]'));
|
|
ok(cargoToml.includes('graph_reasoner'));
|
|
ok(cargoToml.includes('extractors'));
|
|
ok(cargoToml.includes('planner'));
|
|
});
|
|
|
|
test('build scripts are properly configured', async () => {
|
|
const pkg = await import('../package.json', { assert: { type: 'json' } });
|
|
const scripts = pkg.default.scripts;
|
|
|
|
ok(scripts.build);
|
|
ok(scripts['build:wasm']);
|
|
ok(scripts['build:ts']);
|
|
ok(scripts.test);
|
|
ok(scripts.lint);
|
|
ok(scripts.clean);
|
|
|
|
// Check that build script includes WASM and TypeScript builds
|
|
ok(scripts.build.includes('build:wasm'));
|
|
ok(scripts.build.includes('build:ts'));
|
|
});
|
|
|
|
test('dependencies are properly specified', async () => {
|
|
const pkg = await import('../package.json', { assert: { type: 'json' } });
|
|
|
|
// Check required dependencies
|
|
const deps = pkg.default.dependencies;
|
|
ok(deps['@modelcontextprotocol/sdk']);
|
|
ok(deps['fastmcp']);
|
|
ok(deps['commander']);
|
|
ok(deps['express']);
|
|
|
|
// Check dev dependencies
|
|
const devDeps = pkg.default.devDependencies;
|
|
ok(devDeps['typescript']);
|
|
ok(devDeps['wasm-pack']);
|
|
ok(devDeps['@typescript-eslint/eslint-plugin']);
|
|
});
|
|
|
|
test('package exports are correctly defined', async () => {
|
|
const pkg = await import('../package.json', { assert: { type: 'json' } });
|
|
const exports = pkg.default.exports;
|
|
|
|
ok(exports['.']);
|
|
ok(exports['./mcp']);
|
|
ok(exports['./reasoner']);
|
|
ok(exports['./extractors']);
|
|
ok(exports['./planner']);
|
|
ok(exports['./wasm']);
|
|
|
|
// Check that each export has import and types
|
|
Object.values(exports).forEach(exportDef => {
|
|
ok(exportDef.import);
|
|
ok(exportDef.types);
|
|
});
|
|
});
|
|
|
|
test('CLI binaries are properly configured', async () => {
|
|
const pkg = await import('../package.json', { assert: { type: 'json' } });
|
|
const bin = pkg.default.bin;
|
|
|
|
ok(bin['psycho-symbolic-reasoner']);
|
|
ok(bin['psycho-reasoner']);
|
|
ok(bin['psr']);
|
|
|
|
// All should point to the same CLI entry point
|
|
const cliPath = './dist/cli/index.js';
|
|
strictEqual(bin['psycho-symbolic-reasoner'], cliPath);
|
|
});
|
|
|
|
});
|
|
|
|
describe('Simulated Core Functionality Tests', () => {
|
|
|
|
test('sentiment analysis simulation', async () => {
|
|
// Simulate sentiment analysis functionality
|
|
const mockSentimentAnalysis = (text) => {
|
|
const positiveWords = ['happy', 'excited', 'great', 'excellent', 'love'];
|
|
const negativeWords = ['sad', 'angry', 'terrible', 'hate', 'awful'];
|
|
|
|
let score = 0;
|
|
const words = text.toLowerCase().split(' ');
|
|
|
|
words.forEach(word => {
|
|
if (positiveWords.includes(word)) score += 0.2;
|
|
if (negativeWords.includes(word)) score -= 0.2;
|
|
});
|
|
|
|
return {
|
|
score: Math.max(-1, Math.min(1, score)),
|
|
confidence: 0.8,
|
|
primaryEmotion: score > 0 ? 'joy' : score < 0 ? 'sadness' : 'neutral'
|
|
};
|
|
};
|
|
|
|
const result1 = mockSentimentAnalysis("I'm happy and excited about this project");
|
|
ok(result1.score > 0);
|
|
strictEqual(result1.primaryEmotion, 'joy');
|
|
|
|
const result2 = mockSentimentAnalysis("This is terrible and awful");
|
|
ok(result2.score < 0);
|
|
strictEqual(result2.primaryEmotion, 'sadness');
|
|
});
|
|
|
|
test('preference extraction simulation', async () => {
|
|
// Simulate preference extraction
|
|
const mockPreferenceExtraction = (text) => {
|
|
const preferences = [];
|
|
|
|
if (text.includes('like') || text.includes('prefer')) {
|
|
preferences.push({
|
|
type: 'like',
|
|
subject: 'user',
|
|
object: 'extracted_preference',
|
|
strength: 0.8,
|
|
confidence: 0.7
|
|
});
|
|
}
|
|
|
|
if (text.includes('dislike') || text.includes('hate')) {
|
|
preferences.push({
|
|
type: 'dislike',
|
|
subject: 'user',
|
|
object: 'extracted_preference',
|
|
strength: 0.7,
|
|
confidence: 0.8
|
|
});
|
|
}
|
|
|
|
return {
|
|
preferences,
|
|
confidence: 0.75,
|
|
categories: ['general']
|
|
};
|
|
};
|
|
|
|
const result = mockPreferenceExtraction("I like quiet environments but dislike crowded spaces");
|
|
strictEqual(result.preferences.length, 2);
|
|
strictEqual(result.preferences[0].type, 'like');
|
|
strictEqual(result.preferences[1].type, 'dislike');
|
|
});
|
|
|
|
test('graph reasoning simulation', async () => {
|
|
// Simulate graph query functionality
|
|
const mockGraphReasoning = (query) => {
|
|
const mockResults = [
|
|
{ activity: 'meditation', confidence: 0.9 },
|
|
{ activity: 'deep_breathing', confidence: 0.8 },
|
|
{ activity: 'exercise', confidence: 0.7 }
|
|
];
|
|
|
|
return {
|
|
results: mockResults,
|
|
executionTime: 25,
|
|
totalResults: mockResults.length
|
|
};
|
|
};
|
|
|
|
const result = mockGraphReasoning("find stress relief activities");
|
|
ok(Array.isArray(result.results));
|
|
ok(result.results.length > 0);
|
|
ok(result.executionTime > 0);
|
|
|
|
// Check result structure
|
|
const firstResult = result.results[0];
|
|
ok(firstResult.activity);
|
|
ok(typeof firstResult.confidence === 'number');
|
|
});
|
|
|
|
test('planning simulation', async () => {
|
|
// Simulate goal-oriented planning
|
|
const mockPlanning = (goal, state, preferences) => {
|
|
const actions = [
|
|
{
|
|
name: 'Take a break',
|
|
description: 'Step away from work for 10 minutes',
|
|
duration: 10,
|
|
priority: 0.8
|
|
},
|
|
{
|
|
name: 'Practice breathing',
|
|
description: 'Do 4-7-8 breathing exercise',
|
|
duration: 5,
|
|
priority: 0.9
|
|
}
|
|
];
|
|
|
|
return {
|
|
plan: actions,
|
|
confidence: 0.85,
|
|
estimatedDuration: actions.reduce((sum, action) => sum + action.duration, 0),
|
|
explanation: 'Plan designed to address immediate stress relief'
|
|
};
|
|
};
|
|
|
|
const result = mockPlanning(
|
|
'reduce stress',
|
|
{ energy: 'low', stress: 'high' },
|
|
[{ type: 'like', object: 'quick_solutions' }]
|
|
);
|
|
|
|
ok(Array.isArray(result.plan));
|
|
ok(result.plan.length > 0);
|
|
ok(typeof result.confidence === 'number');
|
|
ok(result.explanation);
|
|
|
|
// Check action structure
|
|
const firstAction = result.plan[0];
|
|
ok(firstAction.name);
|
|
ok(firstAction.description);
|
|
ok(typeof firstAction.duration === 'number');
|
|
});
|
|
|
|
});
|
|
|
|
describe('Integration Tests', () => {
|
|
|
|
test('MCP tool integration simulation', async () => {
|
|
// Simulate MCP tool registration and execution
|
|
const mockMCPTools = [
|
|
{
|
|
name: 'extractSentiment',
|
|
description: 'Analyze sentiment from text',
|
|
execute: async (params) => ({
|
|
score: 0.5,
|
|
primaryEmotion: 'joy',
|
|
confidence: 0.8
|
|
})
|
|
},
|
|
{
|
|
name: 'createPlan',
|
|
description: 'Generate action plan',
|
|
execute: async (params) => ({
|
|
plan: [
|
|
{ name: 'Action 1', duration: 10 },
|
|
{ name: 'Action 2', duration: 15 }
|
|
],
|
|
confidence: 0.9
|
|
})
|
|
}
|
|
];
|
|
|
|
// Test tool registration
|
|
strictEqual(mockMCPTools.length, 2);
|
|
|
|
// Test tool execution
|
|
const sentimentTool = mockMCPTools.find(t => t.name === 'extractSentiment');
|
|
const sentimentResult = await sentimentTool.execute({ text: 'Happy text' });
|
|
ok(sentimentResult.score);
|
|
ok(sentimentResult.primaryEmotion);
|
|
|
|
const planTool = mockMCPTools.find(t => t.name === 'createPlan');
|
|
const planResult = await planTool.execute({ goal: 'test goal' });
|
|
ok(Array.isArray(planResult.plan));
|
|
ok(planResult.confidence);
|
|
});
|
|
|
|
test('end-to-end workflow simulation', async () => {
|
|
// Simulate complete workflow: input -> analysis -> planning -> output
|
|
const userInput = "I'm feeling stressed about upcoming deadlines";
|
|
|
|
// Step 1: Sentiment analysis
|
|
const sentiment = {
|
|
score: -0.6,
|
|
primaryEmotion: 'stress',
|
|
confidence: 0.9
|
|
};
|
|
|
|
// Step 2: Preference extraction
|
|
const preferences = {
|
|
preferences: [
|
|
{ type: 'like', object: 'quick_relief', strength: 0.8 }
|
|
]
|
|
};
|
|
|
|
// Step 3: Graph reasoning
|
|
const techniques = {
|
|
results: [
|
|
{ technique: 'deep_breathing', effectiveness: 0.9 },
|
|
{ technique: 'short_break', effectiveness: 0.7 }
|
|
]
|
|
};
|
|
|
|
// Step 4: Planning
|
|
const plan = {
|
|
plan: [
|
|
{ name: 'Deep breathing', duration: 5, priority: 0.9 },
|
|
{ name: 'Take a break', duration: 10, priority: 0.7 }
|
|
],
|
|
confidence: 0.85
|
|
};
|
|
|
|
// Verify workflow
|
|
ok(sentiment.score < 0); // Negative sentiment detected
|
|
strictEqual(sentiment.primaryEmotion, 'stress');
|
|
ok(preferences.preferences.length > 0);
|
|
ok(techniques.results.length > 0);
|
|
ok(plan.plan.length > 0);
|
|
ok(plan.confidence > 0.8);
|
|
});
|
|
|
|
}); |