"""Tests for YOLO mode access control functionality.
This module tests the AccessController class behavior in YOLO modes.
"""
import os
import pytest
from mcp_server_odoo.access_control import AccessControlError, AccessController
from mcp_server_odoo.config import OdooConfig
class TestYoloModeAccessControl:
"""Test access control in YOLO mode."""
@pytest.fixture
def config_yolo_read(self):
"""Create configuration for read-only YOLO mode."""
return OdooConfig(
url=os.getenv("ODOO_URL", "http://localhost:8069"),
username=os.getenv("ODOO_USER", "admin"),
password=os.getenv("ODOO_PASSWORD", "admin"),
database=os.getenv("ODOO_DB"),
yolo_mode="read",
)
@pytest.fixture
def config_yolo_full(self):
"""Create configuration for full access YOLO mode."""
return OdooConfig(
url=os.getenv("ODOO_URL", "http://localhost:8069"),
username=os.getenv("ODOO_USER", "admin"),
password=os.getenv("ODOO_PASSWORD", "admin"),
database=os.getenv("ODOO_DB"),
yolo_mode="true",
)
@pytest.fixture
def config_standard(self):
"""Create configuration for standard mode."""
return OdooConfig(
url=os.getenv("ODOO_URL", "http://localhost:8069"),
api_key="test_api_key",
database=os.getenv("ODOO_DB"),
yolo_mode="off",
)
def test_init_yolo_mode_skips_api_validation(self, config_yolo_read, caplog):
"""Test that YOLO mode skips API key validation."""
# Should not raise error even without API key
AccessController(config_yolo_read)
# Check warning was logged
assert "YOLO mode" in caplog.text
assert "Access control bypassed" in caplog.text
assert "READ-ONLY" in caplog.text
def test_init_yolo_full_mode_warning(self, config_yolo_full, caplog):
"""Test that full YOLO mode shows appropriate warning."""
AccessController(config_yolo_full)
# Check warning was logged
assert "YOLO mode" in caplog.text
assert "FULL ACCESS" in caplog.text
assert "MCP security disabled" in caplog.text
def test_init_standard_mode_without_api_key(self):
"""Test that standard mode with credentials prepares session auth."""
config = OdooConfig(
url=os.getenv("ODOO_URL", "http://localhost:8069"),
username=os.getenv("ODOO_USER", "admin"),
password=os.getenv("ODOO_PASSWORD", "admin"),
database=os.getenv("ODOO_DB"),
yolo_mode="off",
)
controller = AccessController(config, database="testdb")
assert controller.config == config
assert controller._session_id is None
assert controller.database == "testdb"
def test_is_model_enabled_yolo_mode(self, config_yolo_read):
"""Test that all models are enabled in YOLO mode."""
controller = AccessController(config_yolo_read)
# Test various models
assert controller.is_model_enabled("res.partner") is True
assert controller.is_model_enabled("product.product") is True
assert controller.is_model_enabled("ir.model") is True
assert controller.is_model_enabled("any.random.model") is True
def test_get_model_permissions_read_only(self, config_yolo_read):
"""Test permissions in read-only YOLO mode."""
controller = AccessController(config_yolo_read)
permissions = controller.get_model_permissions("res.partner")
assert permissions.model == "res.partner"
assert permissions.enabled is True
assert permissions.can_read is True
assert permissions.can_write is False
assert permissions.can_create is False
assert permissions.can_unlink is False
def test_get_model_permissions_full_access(self, config_yolo_full):
"""Test permissions in full access YOLO mode."""
controller = AccessController(config_yolo_full)
permissions = controller.get_model_permissions("res.partner")
assert permissions.model == "res.partner"
assert permissions.enabled is True
assert permissions.can_read is True
assert permissions.can_write is True
assert permissions.can_create is True
assert permissions.can_unlink is True
def test_check_operation_allowed_read_only(self, config_yolo_read):
"""Test operation checks in read-only YOLO mode."""
controller = AccessController(config_yolo_read)
# Read operations should be allowed
for op in ["read", "search", "search_read", "fields_get", "count", "search_count"]:
allowed, msg = controller.check_operation_allowed("res.partner", op)
assert allowed is True
assert msg is None
# Write operations should be blocked
for op in ["write", "create", "unlink"]:
allowed, msg = controller.check_operation_allowed("res.partner", op)
assert allowed is False
assert "not allowed in read-only YOLO mode" in msg
assert "Only read operations are permitted" in msg
def test_check_operation_allowed_full_access(self, config_yolo_full):
"""Test operation checks in full access YOLO mode."""
controller = AccessController(config_yolo_full)
# All operations should be allowed
for op in ["read", "search", "write", "create", "unlink", "delete"]:
allowed, msg = controller.check_operation_allowed("res.partner", op)
assert allowed is True
assert msg is None
def test_filter_enabled_models_yolo_mode(self, config_yolo_read):
"""Test that all models pass filter in YOLO mode."""
controller = AccessController(config_yolo_read)
models = ["res.partner", "product.product", "ir.model", "custom.model"]
filtered = controller.filter_enabled_models(models)
assert filtered == models # All models should pass
def test_get_enabled_models_yolo_mode(self, config_yolo_read):
"""Test that get_enabled_models returns empty list in YOLO mode."""
controller = AccessController(config_yolo_read)
# Should return empty list (indicating all models allowed)
models = controller.get_enabled_models()
assert models == []
def test_validate_model_access_read_only(self, config_yolo_read):
"""Test validate_model_access in read-only YOLO mode."""
controller = AccessController(config_yolo_read)
# Read should succeed
controller.validate_model_access("res.partner", "read") # Should not raise
# Write should raise
with pytest.raises(AccessControlError, match="not allowed in read-only"):
controller.validate_model_access("res.partner", "write")
def test_validate_model_access_full_access(self, config_yolo_full):
"""Test validate_model_access in full access YOLO mode."""
controller = AccessController(config_yolo_full)
# All operations should succeed
controller.validate_model_access("res.partner", "read") # Should not raise
controller.validate_model_access("res.partner", "write") # Should not raise
controller.validate_model_access("res.partner", "create") # Should not raise
controller.validate_model_access("res.partner", "unlink") # Should not raise
def test_standard_and_yolo_controllers_independent(self, config_yolo_read):
"""Test that standard and YOLO controllers can coexist independently."""
# Create standard mode config without API key
config_no_api = OdooConfig(
url=os.getenv("ODOO_URL", "http://localhost:8069"),
username=os.getenv("ODOO_USER", "admin"),
password=os.getenv("ODOO_PASSWORD", "admin"),
database=os.getenv("ODOO_DB"),
yolo_mode="off", # Standard mode
)
# Standard mode initializes without API key (warns but doesn't crash)
controller_standard = AccessController(config_no_api)
assert controller_standard.config == config_no_api
# Switch to YOLO mode (new controller)
controller_yolo = AccessController(config_yolo_read)
# Should work without API
assert controller_yolo.is_model_enabled("res.partner") is True
if __name__ == "__main__":
pytest.main([__file__, "-v"])