"""Tests for CLI routing utilities."""
import os
import pytest
from basic_memory.cli.commands.routing import force_routing, validate_routing_flags
class TestValidateRoutingFlags:
"""Tests for validate_routing_flags function."""
def test_neither_flag(self):
"""Should not raise when neither flag is set."""
validate_routing_flags(local=False, cloud=False)
def test_local_only(self):
"""Should not raise when only local is set."""
validate_routing_flags(local=True, cloud=False)
def test_cloud_only(self):
"""Should not raise when only cloud is set."""
validate_routing_flags(local=False, cloud=True)
def test_both_flags_raises(self):
"""Should raise ValueError when both flags are set."""
with pytest.raises(ValueError, match="Cannot specify both --local and --cloud"):
validate_routing_flags(local=True, cloud=True)
class TestForceRouting:
"""Tests for force_routing context manager."""
def test_local_sets_env_vars(self):
"""Local flag should set BASIC_MEMORY_FORCE_LOCAL and EXPLICIT_ROUTING."""
os.environ.pop("BASIC_MEMORY_FORCE_LOCAL", None)
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
os.environ.pop("BASIC_MEMORY_EXPLICIT_ROUTING", None)
with force_routing(local=True):
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") == "true"
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") == "true"
# Should be cleaned up after context exits
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") is None
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") is None
def test_cloud_sets_explicit_routing(self):
"""Cloud flag should set FORCE_CLOUD + EXPLICIT_ROUTING and clear FORCE_LOCAL."""
os.environ["BASIC_MEMORY_FORCE_LOCAL"] = "true"
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
os.environ.pop("BASIC_MEMORY_EXPLICIT_ROUTING", None)
with force_routing(cloud=True):
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") is None
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") == "true"
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") == "true"
# Should restore original values after context exits
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") == "true"
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") is None
# Cleanup
os.environ.pop("BASIC_MEMORY_FORCE_LOCAL", None)
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
def test_neither_flag_no_change(self):
"""Neither flag should not change env vars."""
os.environ.pop("BASIC_MEMORY_FORCE_LOCAL", None)
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
os.environ.pop("BASIC_MEMORY_EXPLICIT_ROUTING", None)
with force_routing():
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") is None
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") is None
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") is None
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") is None
def test_preserves_original_env_vars(self):
"""Should restore original env var values after context exits."""
os.environ["BASIC_MEMORY_FORCE_LOCAL"] = "original"
os.environ["BASIC_MEMORY_FORCE_CLOUD"] = "original"
os.environ["BASIC_MEMORY_EXPLICIT_ROUTING"] = "original"
with force_routing(local=True):
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") == "true"
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") == "true"
# Should restore original values
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") == "original"
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") == "original"
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") == "original"
# Cleanup
os.environ.pop("BASIC_MEMORY_FORCE_LOCAL", None)
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
os.environ.pop("BASIC_MEMORY_EXPLICIT_ROUTING", None)
def test_both_flags_raises(self):
"""Should raise ValueError when both flags are set."""
with pytest.raises(ValueError, match="Cannot specify both --local and --cloud"):
with force_routing(local=True, cloud=True):
pass
def test_restores_on_exception(self):
"""Should restore env vars even when exception is raised."""
os.environ.pop("BASIC_MEMORY_FORCE_LOCAL", None)
os.environ.pop("BASIC_MEMORY_FORCE_CLOUD", None)
os.environ.pop("BASIC_MEMORY_EXPLICIT_ROUTING", None)
try:
with force_routing(local=True):
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") == "true"
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") == "true"
raise RuntimeError("Test exception")
except RuntimeError:
pass
# Should be cleaned up even after exception
assert os.environ.get("BASIC_MEMORY_FORCE_LOCAL") is None
assert os.environ.get("BASIC_MEMORY_FORCE_CLOUD") is None
assert os.environ.get("BASIC_MEMORY_EXPLICIT_ROUTING") is None