forked from cardosofelipe/fast-next-template
Add scope management system for hierarchical memory access: - ScopeManager with hierarchy: Global → Project → Agent Type → Agent Instance → Session - ScopePolicy for access control (read, write, inherit permissions) - ScopeResolver for resolving queries across scope hierarchies with inheritance - ScopeFilter for filtering scopes by type, project, or agent - Access control enforcement with parent scope visibility - Deduplication support during resolution across scopes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
362 lines
11 KiB
Python
362 lines
11 KiB
Python
# tests/unit/services/memory/scoping/test_scope.py
|
|
"""Unit tests for scope management."""
|
|
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.services.memory.scoping.scope import (
|
|
ScopeManager,
|
|
ScopePolicy,
|
|
get_scope_manager,
|
|
)
|
|
from app.services.memory.types import ScopeLevel
|
|
|
|
|
|
class TestScopePolicy:
|
|
"""Tests for ScopePolicy dataclass."""
|
|
|
|
def test_default_values(self) -> None:
|
|
"""Test default policy values."""
|
|
policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test-project",
|
|
)
|
|
|
|
assert policy.can_read is True
|
|
assert policy.can_write is True
|
|
assert policy.can_inherit is True
|
|
assert policy.allowed_memory_types == ["all"]
|
|
|
|
def test_allows_read(self) -> None:
|
|
"""Test allows_read method."""
|
|
policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test",
|
|
can_read=True,
|
|
)
|
|
assert policy.allows_read() is True
|
|
|
|
policy.can_read = False
|
|
assert policy.allows_read() is False
|
|
|
|
def test_allows_write(self) -> None:
|
|
"""Test allows_write method."""
|
|
policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test",
|
|
can_write=True,
|
|
)
|
|
assert policy.allows_write() is True
|
|
|
|
policy.can_write = False
|
|
assert policy.allows_write() is False
|
|
|
|
def test_allows_inherit(self) -> None:
|
|
"""Test allows_inherit method."""
|
|
policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test",
|
|
can_inherit=True,
|
|
)
|
|
assert policy.allows_inherit() is True
|
|
|
|
policy.can_inherit = False
|
|
assert policy.allows_inherit() is False
|
|
|
|
def test_allows_memory_type(self) -> None:
|
|
"""Test allows_memory_type method."""
|
|
policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test",
|
|
allowed_memory_types=["all"],
|
|
)
|
|
assert policy.allows_memory_type("working") is True
|
|
assert policy.allows_memory_type("episodic") is True
|
|
|
|
policy.allowed_memory_types = ["working", "episodic"]
|
|
assert policy.allows_memory_type("working") is True
|
|
assert policy.allows_memory_type("episodic") is True
|
|
assert policy.allows_memory_type("semantic") is False
|
|
|
|
|
|
class TestScopeManager:
|
|
"""Tests for ScopeManager class."""
|
|
|
|
@pytest.fixture
|
|
def manager(self) -> ScopeManager:
|
|
"""Create a scope manager."""
|
|
return ScopeManager()
|
|
|
|
def test_create_global_scope(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test creating a global scope."""
|
|
scope = manager.create_scope(
|
|
scope_type=ScopeLevel.GLOBAL,
|
|
scope_id="global",
|
|
)
|
|
|
|
assert scope.scope_type == ScopeLevel.GLOBAL
|
|
assert scope.scope_id == "global"
|
|
assert scope.parent is None
|
|
|
|
def test_create_project_scope(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test creating a project scope."""
|
|
global_scope = manager.create_scope(
|
|
scope_type=ScopeLevel.GLOBAL,
|
|
scope_id="global",
|
|
)
|
|
|
|
project_scope = manager.create_scope(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="project-1",
|
|
parent=global_scope,
|
|
)
|
|
|
|
assert project_scope.scope_type == ScopeLevel.PROJECT
|
|
assert project_scope.scope_id == "project-1"
|
|
assert project_scope.parent is global_scope
|
|
|
|
def test_create_scope_auto_parent(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test that non-global scopes auto-create parent chain."""
|
|
scope = manager.create_scope(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test-project",
|
|
)
|
|
|
|
assert scope.scope_type == ScopeLevel.PROJECT
|
|
assert scope.parent is not None
|
|
assert scope.parent.scope_type == ScopeLevel.GLOBAL
|
|
|
|
def test_create_scope_invalid_hierarchy(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test that invalid hierarchy raises error."""
|
|
project_scope = manager.create_scope(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="project-1",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Invalid scope hierarchy"):
|
|
manager.create_scope(
|
|
scope_type=ScopeLevel.GLOBAL,
|
|
scope_id="global",
|
|
parent=project_scope,
|
|
)
|
|
|
|
def test_create_scope_from_ids(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test creating scope from individual IDs."""
|
|
project_id = uuid4()
|
|
agent_type_id = uuid4()
|
|
|
|
scope = manager.create_scope_from_ids(
|
|
project_id=project_id,
|
|
agent_type_id=agent_type_id,
|
|
)
|
|
|
|
assert scope.scope_type == ScopeLevel.AGENT_TYPE
|
|
assert scope.scope_id == str(agent_type_id)
|
|
assert scope.parent is not None
|
|
assert scope.parent.scope_type == ScopeLevel.PROJECT
|
|
|
|
def test_create_scope_from_ids_with_session(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test creating scope with session ID."""
|
|
project_id = uuid4()
|
|
session_id = "session-123"
|
|
|
|
scope = manager.create_scope_from_ids(
|
|
project_id=project_id,
|
|
session_id=session_id,
|
|
)
|
|
|
|
assert scope.scope_type == ScopeLevel.SESSION
|
|
assert scope.scope_id == session_id
|
|
|
|
def test_get_default_policy(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test getting default policy."""
|
|
scope = manager.create_scope(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test-project",
|
|
)
|
|
|
|
policy = manager.get_policy(scope)
|
|
|
|
assert policy.can_read is True
|
|
assert policy.can_write is True
|
|
|
|
def test_set_and_get_policy(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test setting and retrieving a policy."""
|
|
scope = manager.create_scope(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test-project",
|
|
)
|
|
|
|
custom_policy = ScopePolicy(
|
|
scope_type=ScopeLevel.PROJECT,
|
|
scope_id="test-project",
|
|
can_write=False,
|
|
)
|
|
|
|
manager.set_policy(scope, custom_policy)
|
|
retrieved = manager.get_policy(scope)
|
|
|
|
assert retrieved.can_write is False
|
|
|
|
def test_get_scope_depth(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test getting scope depth."""
|
|
assert manager.get_scope_depth(ScopeLevel.GLOBAL) == 0
|
|
assert manager.get_scope_depth(ScopeLevel.PROJECT) == 1
|
|
assert manager.get_scope_depth(ScopeLevel.AGENT_TYPE) == 2
|
|
assert manager.get_scope_depth(ScopeLevel.AGENT_INSTANCE) == 3
|
|
assert manager.get_scope_depth(ScopeLevel.SESSION) == 4
|
|
|
|
def test_get_parent_level(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test getting parent level."""
|
|
assert manager.get_parent_level(ScopeLevel.GLOBAL) is None
|
|
assert manager.get_parent_level(ScopeLevel.PROJECT) == ScopeLevel.GLOBAL
|
|
assert manager.get_parent_level(ScopeLevel.AGENT_TYPE) == ScopeLevel.PROJECT
|
|
assert manager.get_parent_level(ScopeLevel.SESSION) == ScopeLevel.AGENT_INSTANCE
|
|
|
|
def test_get_child_level(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test getting child level."""
|
|
assert manager.get_child_level(ScopeLevel.GLOBAL) == ScopeLevel.PROJECT
|
|
assert manager.get_child_level(ScopeLevel.PROJECT) == ScopeLevel.AGENT_TYPE
|
|
assert manager.get_child_level(ScopeLevel.SESSION) is None
|
|
|
|
def test_is_ancestor(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test ancestor checking."""
|
|
global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
|
|
project_scope = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project", parent=global_scope
|
|
)
|
|
agent_scope = manager.create_scope(
|
|
ScopeLevel.AGENT_TYPE, "agent", parent=project_scope
|
|
)
|
|
|
|
assert manager.is_ancestor(global_scope, agent_scope) is True
|
|
assert manager.is_ancestor(project_scope, agent_scope) is True
|
|
assert manager.is_ancestor(agent_scope, global_scope) is False
|
|
assert manager.is_ancestor(agent_scope, project_scope) is False
|
|
|
|
def test_get_common_ancestor(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test finding common ancestor."""
|
|
global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
|
|
project_scope = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project", parent=global_scope
|
|
)
|
|
agent1 = manager.create_scope(
|
|
ScopeLevel.AGENT_TYPE, "agent1", parent=project_scope
|
|
)
|
|
agent2 = manager.create_scope(
|
|
ScopeLevel.AGENT_TYPE, "agent2", parent=project_scope
|
|
)
|
|
|
|
common = manager.get_common_ancestor(agent1, agent2)
|
|
|
|
assert common is not None
|
|
assert common.scope_type == ScopeLevel.PROJECT
|
|
|
|
def test_can_access_same_scope(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test access to same scope."""
|
|
scope = manager.create_scope(ScopeLevel.PROJECT, "project")
|
|
|
|
assert manager.can_access(scope, scope) is True
|
|
assert manager.can_access(scope, scope, "write") is True
|
|
|
|
def test_can_access_ancestor(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test access to ancestor scope."""
|
|
global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
|
|
project_scope = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project", parent=global_scope
|
|
)
|
|
|
|
# Child can read from parent
|
|
assert manager.can_access(project_scope, global_scope, "read") is True
|
|
|
|
def test_cannot_access_descendant(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test that parent cannot access child scope."""
|
|
global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
|
|
project_scope = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project", parent=global_scope
|
|
)
|
|
|
|
# Parent cannot access child
|
|
assert manager.can_access(global_scope, project_scope) is False
|
|
|
|
def test_cannot_access_sibling(
|
|
self,
|
|
manager: ScopeManager,
|
|
) -> None:
|
|
"""Test that sibling scopes cannot access each other."""
|
|
global_scope = manager.create_scope(ScopeLevel.GLOBAL, "global")
|
|
project1 = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project1", parent=global_scope
|
|
)
|
|
project2 = manager.create_scope(
|
|
ScopeLevel.PROJECT, "project2", parent=global_scope
|
|
)
|
|
|
|
assert manager.can_access(project1, project2) is False
|
|
assert manager.can_access(project2, project1) is False
|
|
|
|
|
|
class TestGetScopeManager:
|
|
"""Tests for singleton getter."""
|
|
|
|
def test_returns_instance(self) -> None:
|
|
"""Test that getter returns instance."""
|
|
manager = get_scope_manager()
|
|
assert manager is not None
|
|
assert isinstance(manager, ScopeManager)
|
|
|
|
def test_returns_same_instance(self) -> None:
|
|
"""Test that getter returns same instance."""
|
|
manager1 = get_scope_manager()
|
|
manager2 = get_scope_manager()
|
|
assert manager1 is manager2
|