wifi-densepose/vendor/sublinear-time-solver/crates/psycho-symbolic-reasoner/mcp-integration/tests/integration.test.ts

673 lines
21 KiB
TypeScript

import { describe, test, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import { PsychoSymbolicMcpTools } from '../src/tools/index.js';
import { GraphReasonerWrapper } from '../src/wrappers/graph-reasoner.js';
import { TextExtractorWrapper } from '../src/wrappers/text-extractor.js';
import { PlannerWrapper } from '../src/wrappers/planner.js';
import { WasmMemoryManager } from '../src/wasm/memory-manager.js';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Mock WASM paths for testing
const MOCK_WASM_DIR = join(__dirname, '..', 'mock-wasm');
const MOCK_CONFIG = {
graphReasonerWasmPath: join(MOCK_WASM_DIR, 'graph_reasoner.wasm'),
textExtractorWasmPath: join(MOCK_WASM_DIR, 'extractors.wasm'),
plannerWasmPath: join(MOCK_WASM_DIR, 'planner.wasm')
};
// Mock WASM modules for testing
const createMockWasmModule = (moduleName: string) => {
switch (moduleName) {
case 'graph_reasoner':
return {
GraphReasoner: class MockGraphReasoner {
add_fact(subject: string, predicate: string, object: string): string {
return `fact_${Date.now()}`;
}
add_rule(rule_json: string): boolean {
return true;
}
query(query_json: string): string {
return JSON.stringify({
success: true,
results: [{ subject: 'test', predicate: 'is', object: 'working' }],
count: 1,
execution_time_ms: 10
});
}
infer(max_iterations?: number): string {
return JSON.stringify({
new_facts: [{ subject: 'inferred', predicate: 'fact', object: 'test' }],
applied_rules: ['rule_1'],
inference_steps: [],
confidence_scores: {}
});
}
get_graph_stats(): string {
return JSON.stringify({
fact_count: 10,
rule_count: 5,
entity_count: 15
});
}
free(): void {}
},
memory: new WebAssembly.Memory({ initial: 256 }),
__wbindgen_malloc: (size: number) => 0,
__wbindgen_free: (ptr: number, size: number) => {},
__wbindgen_realloc: (ptr: number, oldSize: number, newSize: number) => 0
};
case 'text_extractor':
return {
TextExtractor: class MockTextExtractor {
analyze_sentiment(text: string): string {
return JSON.stringify({
overall_sentiment: 'positive',
confidence: 0.85,
scores: { positive: 0.85, negative: 0.1, neutral: 0.05 }
});
}
extract_preferences(text: string): string {
return JSON.stringify({
preferences: [
{ category: 'food', preference: 'pizza', strength: 'strong', confidence: 0.9 }
],
confidence: 0.8,
categories: ['food']
});
}
detect_emotions(text: string): string {
return JSON.stringify({
primary_emotion: 'joy',
emotions: [{ emotion: 'joy', score: 0.8, confidence: 0.9 }],
intensity: 0.7,
confidence: 0.85
});
}
analyze_all(text: string): string {
return JSON.stringify({
sentiment: { overall_sentiment: 'positive', confidence: 0.85 },
preferences: { preferences: [], confidence: 0.5 },
emotions: { primary_emotion: 'joy', confidence: 0.8 }
});
}
free(): void {}
},
memory: new WebAssembly.Memory({ initial: 256 }),
__wbindgen_malloc: (size: number) => 0,
__wbindgen_free: (ptr: number, size: number) => {},
__wbindgen_realloc: (ptr: number, oldSize: number, newSize: number) => 0
};
case 'planner_system':
return {
PlannerSystem: class MockPlannerSystem {
set_state(key: string, value: string): boolean {
return true;
}
get_state(key: string): string {
return JSON.stringify('test_value');
}
add_action(action_json: string): boolean {
return true;
}
add_goal(goal_json: string): boolean {
return true;
}
plan(goal_id: string): string {
return JSON.stringify({
success: true,
plan: {
id: 'plan_1',
goal_id: goal_id,
steps: [
{
step_number: 1,
action_id: 'action_1',
description: 'Test action',
estimated_cost: 10,
estimated_duration: 5
}
],
total_cost: 10,
estimated_duration: 5,
created_at: new Date().toISOString()
}
});
}
plan_to_state(target_state_json: string): string {
return this.plan('temp_goal');
}
execute_plan(plan_json: string): string {
return JSON.stringify({
success: true,
executed_steps: [],
final_state: { states: {} },
total_cost: 0
});
}
add_rule(rule_json: string): boolean {
return true;
}
evaluate_rules(): string {
return JSON.stringify([]);
}
get_world_state(): string {
return JSON.stringify({ states: { test_key: 'test_value' } });
}
get_available_actions(): string {
return JSON.stringify([]);
}
free(): void {}
},
memory: new WebAssembly.Memory({ initial: 256 }),
__wbindgen_malloc: (size: number) => 0,
__wbindgen_free: (ptr: number, size: number) => {},
__wbindgen_realloc: (ptr: number, oldSize: number, newSize: number) => 0
};
default:
throw new Error(`Unknown module: ${moduleName}`);
}
};
// Mock the WASM loader
jest.mock('../src/wasm/loader.js', () => {
return {
WasmLoader: {
getInstance: () => ({
loadGraphReasoner: async () => createMockWasmModule('graph_reasoner'),
loadTextExtractor: async () => createMockWasmModule('text_extractor'),
loadPlannerSystem: async () => createMockWasmModule('planner_system'),
isModuleLoaded: () => true,
getModule: (name: string) => createMockWasmModule(name),
unloadModule: () => true,
unloadAllModules: () => {},
getMemoryStats: () => ({ loadedModules: 3, loadingModules: 0, moduleNames: ['graph_reasoner', 'text_extractor', 'planner_system'] })
})
}
};
});
describe('Psycho-Symbolic Reasoner MCP Integration', () => {
let mcpTools: PsychoSymbolicMcpTools;
let memoryManager: WasmMemoryManager;
beforeAll(async () => {
// Initialize with mock configuration
mcpTools = new PsychoSymbolicMcpTools();
memoryManager = WasmMemoryManager.getInstance();
await mcpTools.initialize(MOCK_CONFIG);
});
afterAll(() => {
mcpTools.cleanup();
});
beforeEach(() => {
// Clean up instances between tests
memoryManager.cleanup();
});
describe('MCP Tools Initialization', () => {
test('should initialize successfully', () => {
expect(mcpTools.isInitialized()).toBe(true);
});
test('should return health status', () => {
const health = mcpTools.getHealthStatus();
expect(health.initialized).toBe(true);
expect(health.memoryStats).toBeDefined();
expect(health.activeInstances).toBeDefined();
});
test('should list available tools', () => {
const tools = mcpTools.getTools();
expect(tools).toBeInstanceOf(Array);
expect(tools.length).toBeGreaterThan(0);
// Check for key tools
const toolNames = tools.map(t => t.name);
expect(toolNames).toContain('queryGraph');
expect(toolNames).toContain('extractAffect');
expect(toolNames).toContain('extractPreferences');
expect(toolNames).toContain('extractEmotions');
expect(toolNames).toContain('planAction');
});
});
describe('Graph Reasoner Tools', () => {
test('should add facts successfully', async () => {
const result = await mcpTools.callTool({
name: 'addFact',
arguments: {
fact: {
subject: 'John',
predicate: 'likes',
object: 'pizza'
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.factId).toBeDefined();
});
test('should query graph successfully', async () => {
const result = await mcpTools.callTool({
name: 'queryGraph',
arguments: {
query: {
pattern: {
subject: 'John',
predicate: 'likes'
}
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.results).toBeInstanceOf(Array);
});
test('should add rules successfully', async () => {
const result = await mcpTools.callTool({
name: 'addRule',
arguments: {
rule: {
id: 'test_rule',
name: 'Test Rule',
conditions: [
{
type: 'fact',
pattern: { subject: '?x', predicate: 'likes', object: 'pizza' }
}
],
conclusions: [
{
type: 'fact',
content: { subject: '?x', predicate: 'is', object: 'pizza_lover' }
}
]
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
});
test('should run inference successfully', async () => {
const result = await mcpTools.callTool({
name: 'runInference',
arguments: {
options: {
maxIterations: 5
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.new_facts).toBeInstanceOf(Array);
expect(response.applied_rules).toBeInstanceOf(Array);
});
});
describe('Text Extractor Tools', () => {
test('should extract sentiment successfully', async () => {
const result = await mcpTools.callTool({
name: 'extractAffect',
arguments: {
text: 'I love this amazing product! It makes me so happy.'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.overall_sentiment).toBeDefined();
expect(response.confidence).toBeDefined();
expect(response.scores).toBeDefined();
});
test('should extract preferences successfully', async () => {
const result = await mcpTools.callTool({
name: 'extractPreferences',
arguments: {
text: 'I really enjoy Italian food, especially pizza and pasta. I also love watching movies.'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.preferences).toBeInstanceOf(Array);
expect(response.confidence).toBeDefined();
});
test('should detect emotions successfully', async () => {
const result = await mcpTools.callTool({
name: 'extractEmotions',
arguments: {
text: 'I am so excited about this new opportunity! It fills me with joy and anticipation.'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.primary_emotion).toBeDefined();
expect(response.emotions).toBeInstanceOf(Array);
expect(response.confidence).toBeDefined();
});
test('should analyze text comprehensively', async () => {
const result = await mcpTools.callTool({
name: 'analyzeText',
arguments: {
text: 'I absolutely love pizza! It\'s my favorite food and always makes me happy.',
includeSentiment: true,
includePreferences: true,
includeEmotions: true
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.sentiment).toBeDefined();
expect(response.preferences).toBeDefined();
expect(response.emotions).toBeDefined();
});
});
describe('Planner Tools', () => {
test('should set state successfully', async () => {
const result = await mcpTools.callTool({
name: 'setState',
arguments: {
key: 'player_health',
value: 100
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
});
test('should get state successfully', async () => {
const result = await mcpTools.callTool({
name: 'getState',
arguments: {
key: 'player_health'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.key).toBe('player_health');
expect(response.value).toBeDefined();
});
test('should add actions successfully', async () => {
const result = await mcpTools.callTool({
name: 'addAction',
arguments: {
action: {
id: 'move_forward',
name: 'Move Forward',
description: 'Move the player forward one step',
preconditions: [
{
state_key: 'can_move',
required_value: true
}
],
effects: [
{
state_key: 'position_x',
value: 1
}
],
cost: {
base_cost: 1
}
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
});
test('should add goals successfully', async () => {
const result = await mcpTools.callTool({
name: 'addGoal',
arguments: {
goal: {
id: 'reach_destination',
name: 'Reach Destination',
description: 'Get to the target location',
conditions: [
{
state_key: 'position_x',
target_value: 10
},
{
state_key: 'position_y',
target_value: 5
}
],
priority: 'high'
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
});
test('should create action plan successfully', async () => {
const result = await mcpTools.callTool({
name: 'planAction',
arguments: {
goalId: 'reach_destination',
options: {
maxDepth: 10,
heuristic: 'astar'
}
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
if (response.plan) {
expect(response.plan.steps).toBeInstanceOf(Array);
expect(response.plan.total_cost).toBeDefined();
}
});
});
describe('Utility Tools', () => {
test('should get memory stats', async () => {
const result = await mcpTools.callTool({
name: 'getMemoryStats',
arguments: {}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.totalInstances).toBeDefined();
expect(response.instancesByType).toBeDefined();
});
test('should create instances', async () => {
const result = await mcpTools.callTool({
name: 'createInstance',
arguments: {
type: 'graph_reasoner',
instanceId: 'test_graph_instance'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.instanceId).toBe('test_graph_instance');
expect(response.type).toBe('graph_reasoner');
});
test('should remove instances', async () => {
// First create an instance
await mcpTools.callTool({
name: 'createInstance',
arguments: {
type: 'text_extractor',
instanceId: 'test_remove_instance'
}
});
// Then remove it
const result = await mcpTools.callTool({
name: 'removeInstance',
arguments: {
instanceId: 'test_remove_instance'
}
});
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.instanceId).toBe('test_remove_instance');
});
});
describe('Error Handling', () => {
test('should handle invalid tool names', async () => {
const result = await mcpTools.callTool({
name: 'nonexistentTool',
arguments: {}
});
expect(result.isError).toBe(true);
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.error).toBeDefined();
expect(response.code).toBe('INVALID_INPUT_ERROR');
});
test('should handle invalid arguments', async () => {
const result = await mcpTools.callTool({
name: 'addFact',
arguments: {
fact: {
// Missing required fields
subject: 'John'
// predicate and object are missing
}
}
});
expect(result.isError).toBe(true);
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.error).toBeDefined();
});
test('should handle missing instance ID gracefully', async () => {
const result = await mcpTools.callTool({
name: 'queryGraph',
arguments: {
query: {
pattern: {
subject: 'test'
}
},
instanceId: 'nonexistent_instance'
}
});
expect(result.isError).toBe(true);
expect(result.content[0].type).toBe('text');
const response = JSON.parse(result.content[0].text);
expect(response.error).toBeDefined();
});
});
describe('Integration Scenarios', () => {
test('should handle complex workflow', async () => {
// 1. Analyze text to extract preferences and emotions
const textAnalysis = await mcpTools.callTool({
name: 'analyzeText',
arguments: {
text: 'I love Italian food and it makes me feel happy and excited!'
}
});
expect(textAnalysis.content[0].type).toBe('text');
const analysis = JSON.parse(textAnalysis.content[0].text);
// 2. Add facts based on analysis to knowledge graph
if (analysis.preferences && analysis.preferences.preferences.length > 0) {
const addFactResult = await mcpTools.callTool({
name: 'addFact',
arguments: {
fact: {
subject: 'user',
predicate: 'likes',
object: analysis.preferences.preferences[0].preference
}
}
});
expect(addFactResult.content[0].type).toBe('text');
const factResponse = JSON.parse(addFactResult.content[0].text);
expect(factResponse.success).toBe(true);
}
// 3. Set planner state based on emotional state
if (analysis.emotions && analysis.emotions.primary_emotion) {
const setStateResult = await mcpTools.callTool({
name: 'setState',
arguments: {
key: 'user_mood',
value: analysis.emotions.primary_emotion
}
});
expect(setStateResult.content[0].type).toBe('text');
const stateResponse = JSON.parse(setStateResult.content[0].text);
expect(stateResponse.success).toBe(true);
}
// 4. Query knowledge graph for related information
const queryResult = await mcpTools.callTool({
name: 'queryGraph',
arguments: {
query: {
pattern: {
subject: 'user',
predicate: 'likes'
}
}
}
});
expect(queryResult.content[0].type).toBe('text');
const queryResponse = JSON.parse(queryResult.content[0].text);
expect(queryResponse.success).toBe(true);
});
});
});