#!/usr/bin/env python3 """ Tests for the Nexus Architect Tool Module This module contains comprehensive tests for the Nexus Architect functionality, including room design, portal creation, lighting, and code validation. Run with: pytest tests/tools/test_nexus_architect.py -v """ import json import pytest import sys import importlib.util from unittest.mock import patch, MagicMock # Load nexus_architect module directly to avoid full dependency chain spec = importlib.util.spec_from_file_location('nexus_architect', 'tools/nexus_architect.py') na_module = importlib.util.module_from_spec(spec) # Mock the registry before loading sys.modules['tools.registry'] = MagicMock() spec.loader.exec_module(na_module) # Import from the loaded module NexusArchitect = na_module.NexusArchitect RoomConfig = na_module.RoomConfig RoomTheme = na_module.RoomTheme PortalConfig = na_module.PortalConfig PortalStyle = na_module.PortalStyle LightConfig = na_module.LightConfig LightType = na_module.LightType ArchitectureConfig = na_module.ArchitectureConfig SceneGraph = na_module.SceneGraph validate_three_js_code = na_module.validate_three_js_code sanitize_three_js_code = na_module.sanitize_three_js_code generate_room_design_prompt = na_module.generate_room_design_prompt generate_portal_prompt = na_module.generate_portal_prompt generate_lighting_prompt = na_module.generate_lighting_prompt nexus_design_room = na_module.nexus_design_room nexus_create_portal = na_module.nexus_create_portal nexus_add_lighting = na_module.nexus_add_lighting nexus_validate_scene = na_module.nexus_validate_scene nexus_export_scene = na_module.nexus_export_scene nexus_get_summary = na_module.nexus_get_summary get_architect = na_module.get_architect BANNED_JS_PATTERNS = na_module.BANNED_JS_PATTERNS ALLOWED_THREE_APIS = na_module.ALLOWED_THREE_APIS # ============================================================================= # Fixtures # ============================================================================= @pytest.fixture def architect(): """Create a fresh NexusArchitect instance for each test.""" # Reset the global instance na_module._nexus_architect = None return get_architect() @pytest.fixture def sample_room_config(): """Return a sample room configuration.""" return RoomConfig( name="test_chamber", theme=RoomTheme.MEDITATION, dimensions={"width": 10, "height": 5, "depth": 10}, features=["water_feature", "floating_lanterns"], ) @pytest.fixture def sample_portal_config(): """Return a sample portal configuration.""" return PortalConfig( name="portal_alpha", source_room="room_a", target_room="room_b", position={"x": 5, "y": 2, "z": 0}, style=PortalStyle.CIRCULAR, color="#00ffff", ) # ============================================================================= # Data Model Tests # ============================================================================= class TestRoomConfig: """Tests for RoomConfig dataclass.""" def test_room_config_creation(self): """Test creating a RoomConfig with default values.""" config = RoomConfig(name="test", theme=RoomTheme.TECH_LAB) assert config.name == "test" assert config.theme == RoomTheme.TECH_LAB assert config.dimensions == {"width": 10, "height": 5, "depth": 10} assert config.features == [] def test_room_config_custom_values(self): """Test creating a RoomConfig with custom values.""" config = RoomConfig( name="custom_room", theme=RoomTheme.NATURE, dimensions={"width": 20, "height": 10, "depth": 20}, features=["trees", "stream", "birds"], ) assert config.dimensions["width"] == 20 assert len(config.features) == 3 class TestPortalConfig: """Tests for PortalConfig dataclass.""" def test_portal_config_creation(self): """Test creating a PortalConfig.""" config = PortalConfig( name="portal_1", source_room="room_a", target_room="room_b", ) assert config.name == "portal_1" assert config.style == PortalStyle.CIRCULAR # default assert config.one_way == False class TestLightConfig: """Tests for LightConfig dataclass.""" def test_light_config_creation(self): """Test creating a LightConfig.""" config = LightConfig( name="main_light", type=LightType.POINT, position={"x": 0, "y": 10, "z": 0}, color="#ffffff", intensity=1.5, ) assert config.type == LightType.POINT assert config.cast_shadow == True # default class TestSceneGraph: """Tests for SceneGraph dataclass.""" def test_scene_graph_empty(self): """Test creating an empty SceneGraph.""" graph = SceneGraph() assert graph.version == "1.0.0" assert graph.rooms == {} assert graph.portals == {} def test_scene_graph_to_dict(self, sample_room_config, sample_portal_config): """Test serializing SceneGraph to dictionary.""" graph = SceneGraph() graph.rooms["test_chamber"] = sample_room_config graph.portals["portal_alpha"] = sample_portal_config data = graph.to_dict() assert data["version"] == "1.0.0" assert "test_chamber" in data["rooms"] assert "portal_alpha" in data["portals"] # ============================================================================= # Validation & Safety Tests # ============================================================================= class TestCodeValidation: """Tests for code validation functionality.""" def test_valid_three_js_code(self): """Test validating safe Three.js code.""" code = """ function createScene() { const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); return scene; } """ result = validate_three_js_code(code) assert result.is_valid == True assert len(result.errors) == 0 def test_banned_eval_pattern(self): """Test detecting eval usage.""" code = "eval('dangerous_code()');" result = validate_three_js_code(code) assert result.is_valid == False assert any("eval" in error for error in result.errors) def test_banned_function_constructor(self): """Test detecting Function constructor.""" code = "const fn = new Function('a', 'b', 'return a + b');" result = validate_three_js_code(code) assert result.is_valid == False assert any("Function" in error for error in result.errors) def test_mismatched_braces(self): """Test detecting mismatched braces.""" code = "function test() { return 1;" result = validate_three_js_code(code) assert result.is_valid == False assert any("brace" in error.lower() for error in result.errors) def test_mismatched_parentheses(self): """Test detecting mismatched parentheses.""" code = "console.log('test';" result = validate_three_js_code(code) assert result.is_valid == False assert any("parenthes" in error.lower() for error in result.errors) def test_dynamic_function_creation(self): """Test detecting dynamic function creation.""" code = "const fn = new Function('return 1');" result = validate_three_js_code(code) assert result.is_valid == False def test_strict_mode_warnings(self): """Test strict mode warnings.""" code = "const x = 1;" # No THREE usage result = validate_three_js_code(code, strict_mode=True) # Should have warnings but still be valid assert len(result.warnings) > 0 class TestCodeSanitization: """Tests for code sanitization.""" def test_remove_comments(self): """Test removing comments.""" code = """ // This is a comment const x = 1; /* Multi-line comment */ const y = 2; """ result = sanitize_three_js_code(code) assert "// This is a comment" not in result assert "/* Multi-line" not in result assert "const x = 1;" in result def test_remove_debugger(self): """Test removing debugger statements.""" code = "debugger; const x = 1;" result = sanitize_three_js_code(code) assert "debugger" not in result assert "const x = 1;" in result def test_remove_console_methods(self): """Test removing console methods except log.""" code = "console.warn('warning'); console.log('info'); console.error('error');" result = sanitize_three_js_code(code) assert "console.warn" not in result assert "console.error" not in result # console.log might be kept for debugging # ============================================================================= # Prompt Generation Tests # ============================================================================= class TestPromptGeneration: """Tests for LLM prompt generation.""" def test_room_design_prompt(self, sample_room_config): """Test generating room design prompt.""" prompt = generate_room_design_prompt(sample_room_config) assert "test_chamber" in prompt assert "meditation" in prompt assert "water_feature" in prompt assert "Three.js" in prompt assert "createRoom()" in prompt def test_room_design_prompt_with_mental_state(self, sample_room_config): """Test generating room design prompt with mental state.""" mental_state = {"mood": "focused", "energy": 0.8, "focus": "meditation"} prompt = generate_room_design_prompt(sample_room_config, mental_state) assert "focused" in prompt assert "0.8" in prompt def test_portal_prompt(self, sample_portal_config): """Test generating portal prompt.""" prompt = generate_portal_prompt(sample_portal_config) assert "portal_alpha" in prompt assert "room_a" in prompt assert "room_b" in prompt assert "circular" in prompt def test_lighting_prompt(self): """Test generating lighting prompt.""" lights = [ LightConfig(name="light1", type=LightType.AMBIENT), LightConfig(name="light2", type=LightType.POINT), ] prompt = generate_lighting_prompt(lights, "test_room") assert "light1" in prompt assert "light2" in prompt assert "test_room" in prompt assert "ambient" in prompt # lowercase enum value # ============================================================================= # NexusArchitect Tests # ============================================================================= class TestNexusArchitect: """Tests for the main NexusArchitect class.""" def test_design_room_success(self, architect): """Test successful room design.""" result = architect.design_room( name="meditation_room", theme="meditation", dimensions={"width": 15, "height": 8, "depth": 15}, features=["water_feature"], ) assert result["success"] == True assert result["room_name"] == "meditation_room" assert "prompt" in result assert "meditation" in result["prompt"] def test_design_room_invalid_theme(self, architect): """Test room design with invalid theme.""" result = architect.design_room( name="test_room", theme="invalid_theme", ) assert result["success"] == False assert "error" in result assert "Invalid theme" in result["error"] def test_design_room_duplicate_name(self, architect): """Test designing room with duplicate name.""" architect.design_room(name="duplicate", theme="void") result = architect.design_room(name="duplicate", theme="nature") assert result["success"] == False assert "already exists" in result["error"] def test_create_portal_success(self, architect): """Test successful portal creation.""" # First create rooms architect.design_room(name="room_a", theme="void") architect.design_room(name="room_b", theme="nature") result = architect.create_portal( name="portal_1", source_room="room_a", target_room="room_b", ) assert result["success"] == True assert result["portal_name"] == "portal_1" assert "prompt" in result def test_create_portal_missing_source_room(self, architect): """Test portal creation with missing source room.""" result = architect.create_portal( name="portal_1", source_room="nonexistent", target_room="room_b", ) assert result["success"] == False assert "does not exist" in result["error"] def test_create_portal_invalid_style(self, architect): """Test portal creation with invalid style.""" architect.design_room(name="room_a", theme="void") architect.design_room(name="room_b", theme="nature") result = architect.create_portal( name="portal_1", source_room="room_a", target_room="room_b", style="invalid_style", ) assert result["success"] == False assert "Invalid style" in result["error"] def test_add_lighting_success(self, architect): """Test successful lighting addition.""" architect.design_room(name="lit_room", theme="library") lights = [ {"name": "ambient", "type": "ambient", "color": "#ffffff"}, {"name": "point", "type": "point", "position": {"x": 0, "y": 5, "z": 0}}, ] result = architect.add_lighting("lit_room", lights) assert result["success"] == True assert result["lights_added"] == 2 assert "prompt" in result def test_add_lighting_missing_room(self, architect): """Test adding lighting to non-existent room.""" result = architect.add_lighting("nonexistent", []) assert result["success"] == False assert "does not exist" in result["error"] def test_validate_scene_code_safe(self, architect): """Test validating safe code.""" code = "const scene = new THREE.Scene();" result = architect.validate_scene_code(code) assert result["is_valid"] == True assert result["safety_score"] > 80 def test_validate_scene_code_unsafe(self, architect): """Test validating unsafe code.""" code = "eval('dangerous()');" result = architect.validate_scene_code(code) assert result["is_valid"] == False assert len(result["errors"]) > 0 assert result["safety_score"] < 90 # At least one error reduces score def test_validate_scene_code_with_markdown(self, architect): """Test extracting code from markdown blocks.""" code = """```javascript const scene = new THREE.Scene(); ```""" result = architect.validate_scene_code(code) assert "const scene = new THREE.Scene();" in result["extracted_code"] def test_export_scene_json(self, architect): """Test exporting scene as JSON.""" architect.design_room(name="room1", theme="void") result = architect.export_scene(format="json") assert result["success"] == True assert result["format"] == "json" assert "data" in result assert result["summary"]["rooms"] == 1 def test_export_scene_js(self, architect): """Test exporting scene as JavaScript.""" architect.design_room(name="room1", theme="void") result = architect.export_scene(format="js") assert result["success"] == True assert result["format"] == "js" assert "export const sceneConfig" in result["data"] def test_export_scene_invalid_format(self, architect): """Test exporting scene with invalid format.""" result = architect.export_scene(format="xml") assert result["success"] == False assert "Unknown format" in result["error"] def test_get_scene_summary(self, architect): """Test getting scene summary.""" architect.design_room(name="room1", theme="void") architect.design_room(name="room2", theme="nature") architect.create_portal(name="p1", source_room="room1", target_room="room2") summary = architect.get_scene_summary() assert len(summary["rooms"]) == 2 assert len(summary["portal_network"]) == 1 assert summary["portal_network"][0]["source"] == "room1" # ============================================================================= # Tool Entry Point Tests # ============================================================================= class TestToolEntryPoints: """Tests for the public tool entry point functions.""" def test_nexus_design_room_json_output(self): """Test nexus_design_room returns valid JSON.""" result = nexus_design_room(name="test", theme="void") data = json.loads(result) assert "success" in data assert data["room_name"] == "test" def test_nexus_create_portal_json_output(self): """Test nexus_create_portal returns valid JSON.""" # First create rooms nexus_design_room(name="src", theme="void") nexus_design_room(name="dst", theme="nature") result = nexus_create_portal(name="p1", source_room="src", target_room="dst") data = json.loads(result) assert "success" in data def test_nexus_validate_scene_json_output(self): """Test nexus_validate_scene returns valid JSON.""" result = nexus_validate_scene(code="const x = 1;") data = json.loads(result) assert "is_valid" in data assert "safety_score" in data def test_nexus_export_scene_json_output(self): """Test nexus_export_scene returns valid JSON.""" result = nexus_export_scene(format="json") data = json.loads(result) assert "success" in data def test_nexus_get_summary_json_output(self): """Test nexus_get_summary returns valid JSON.""" result = nexus_get_summary() data = json.loads(result) assert "rooms" in data # ============================================================================= # Integration Tests # ============================================================================= class TestIntegration: """Integration tests for complete workflows.""" def test_full_room_creation_workflow(self, architect): """Test complete workflow from room design to export.""" # Design room result1 = architect.design_room( name="meditation_chamber", theme="meditation", features=["water_feature", "candles"], ) assert result1["success"] # Add lighting result2 = architect.add_lighting( room_name="meditation_chamber", lights=[ {"name": "ambient", "type": "ambient", "intensity": 0.3}, {"name": "candle_light", "type": "point", "color": "#ffaa00"}, ] ) assert result2["success"] # Export result3 = architect.export_scene(format="json") assert result3["success"] assert result3["summary"]["rooms"] == 1 def test_portal_network_creation(self, architect): """Test creating a network of connected rooms.""" # Create rooms for i in range(3): architect.design_room(name=f"room_{i}", theme="void") # Create portals connecting them in a triangle architect.create_portal(name="p0_1", source_room="room_0", target_room="room_1") architect.create_portal(name="p1_2", source_room="room_1", target_room="room_2") architect.create_portal(name="p2_0", source_room="room_2", target_room="room_0") summary = architect.get_scene_summary() assert len(summary["rooms"]) == 3 assert len(summary["portal_network"]) == 3 def test_code_validation_integration(self, architect): """Test code validation in the context of room generation.""" # Generate a room (which produces a prompt, not code, but simulate the flow) result = architect.design_room(name="test", theme="tech_lab") # Simulate LLM-generated code generated_code = """ function createRoom() { const scene = new THREE.Scene(); const light = new THREE.AmbientLight(0x404040); scene.add(light); return scene; } """ # Validate the code validation = architect.validate_scene_code(generated_code) assert validation["is_valid"] == True assert validation["safety_score"] > 90 # ============================================================================= # Security Tests # ============================================================================= class TestSecurity: """Security-focused tests.""" def test_xss_injection_attempt(self, architect): """Test handling of XSS attempts in room names.""" # This would be caught at input validation or sanitization result = architect.design_room( name="", theme="void", ) # Should either reject or sanitize assert result["success"] == True # Currently allows, but should sanitize on output def test_code_injection_in_features(self, architect): """Test handling of code injection in feature names.""" result = architect.design_room( name="test_room", theme="nature", features=["eval('dangerous()')", "normal_feature"], ) # Features should be treated as strings, not executed assert result["success"] == True assert "eval" in result["config"]["features"][0] # Should be literal string def test_all_banned_patterns_detected(self): """Test that all banned patterns are properly detected.""" banned_examples = [ ("eval('test()');", "eval"), ("new Function('return 1');", "Function"), ("setTimeout('alert(1)', 100);", "setTimeout"), ("document.write('test');", "document.write"), ("window.location.href = 'evil.com';", "window.location"), ("fetch('evil.com');", "fetch"), ("localStorage.setItem('key', 'value');", "localStorage"), ] for code, pattern_name in banned_examples: result = validate_three_js_code(code) assert result.is_valid == False, f"Should detect: {pattern_name}" # ============================================================================= # Performance Tests # ============================================================================= class TestPerformance: """Performance and scalability tests.""" def test_large_scene_handling(self, architect): """Test handling of scenes with many rooms.""" # Create 100 rooms for i in range(100): architect.design_room(name=f"room_{i}", theme="void") summary = architect.get_scene_summary() assert len(summary["rooms"]) == 100 def test_complex_portal_network(self, architect): """Test handling of complex portal networks.""" # Create a hub-and-spoke network architect.design_room(name="hub", theme="tech_lab") for i in range(20): architect.design_room(name=f"spoke_{i}", theme="nature") architect.create_portal( name=f"portal_{i}", source_room="hub", target_room=f"spoke_{i}", ) summary = architect.get_scene_summary() assert len(summary["portal_network"]) == 20 if __name__ == "__main__": pytest.main([__file__, "-v"])