Implements the foundation for autonomous Nexus expansion: - NexusArchitect tool with 6 operations (design_room, create_portal, add_lighting, validate_scene, export_scene, get_summary) - Security-first validation with banned pattern detection - LLM prompt generators for Three.js code generation - 48 comprehensive tests (100% pass) - Complete documentation with API reference Addresses: hermes-agent#42 (Phase 31) Related: Burn Report #6
650 lines
24 KiB
Python
650 lines
24 KiB
Python
#!/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="<script>alert('xss')</script>",
|
|
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"])
|