File size: 7,627 Bytes
b6ae7b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/usr/bin/env python3
"""
Unit Tests for Stack 2.9 Memory & Context Module
"""

import pytest
import sys
import json
from pathlib import Path
from unittest.mock import MagicMock, patch
from datetime import datetime

# Add stack_cli to path
sys.path.insert(0, str(Path(__file__).parent.parent / "stack_cli"))

from stack_cli.context import (
    ProjectContext,
    SessionMemory,
    ContextManager,
    ProjectAware,
    create_context_manager
)


class TestProjectContext:
    """Test ProjectContext dataclass."""

    def test_project_context_creation(self):
        """Test creating a project context."""
        ctx = ProjectContext(
            name="test_project",
            path="/path/to/project",
            language="python",
            framework="fastapi"
        )
        
        assert ctx.name == "test_project"
        assert ctx.path == "/path/to/project"
        assert ctx.language == "python"
        assert ctx.framework == "fastapi"
        assert ctx.files == []
        assert ctx.dirs == []

    def test_project_context_defaults(self):
        """Test default values."""
        ctx = ProjectContext(name="test", path="/test")
        
        assert ctx.language is None
        assert ctx.framework is None
        assert ctx.has_git is False


class TestSessionMemory:
    """Test SessionMemory class."""

    def test_session_memory_creation(self):
        """Test creating session memory."""
        session = SessionMemory()
        
        assert session.messages == []
        assert session.tools_used == []
        assert session.files_touched == []
        assert session.commands_run == []

    def test_add_message(self):
        """Test adding a message."""
        session = SessionMemory()
        session.add_message("user", "Hello")
        
        assert len(session.messages) == 1
        assert session.messages[0]["role"] == "user"
        assert session.messages[0]["content"] == "Hello"

    def test_add_tool_usage(self):
        """Test recording tool usage."""
        session = SessionMemory()
        session.add_tool_usage("read", {"success": True})
        
        assert len(session.tools_used) == 1
        assert session.tools_used[0]["tool"] == "read"

    def test_add_file_touched(self):
        """Test recording file access."""
        session = SessionMemory()
        session.add_file_touched("test.py", "read")
        
        assert len(session.files_touched) == 1
        assert session.files_touched[0]["path"] == "test.py"

    def test_add_command(self):
        """Test recording command execution."""
        session = SessionMemory()
        session.add_command("ls -la", {"success": True})
        
        assert len(session.commands_run) == 1
        assert session.commands_run[0]["command"] == "ls -la"

    def test_get_summary(self):
        """Test getting session summary."""
        session = SessionMemory()
        session.add_message("user", "Hello")
        session.add_tool_usage("read", {"success": True})
        
        summary = session.get_summary()
        
        assert summary["messages_count"] == 1
        assert summary["tools_used_count"] == 1
        assert "duration_minutes" in summary


class TestContextManager:
    """Test ContextManager class."""

    @patch('stack_cli.context.Path')
    def test_context_manager_creation(self, mock_path):
        """Test creating context manager."""
        with patch.object(Path, 'exists', return_value=False):
            cm = ContextManager("/tmp/test")
        
        assert cm is not None
        assert cm.workspace == Path("/tmp/test")

    @patch('stack_cli.context.Path')
    def test_load_context(self, mock_path):
        """Test loading context files."""
        with patch.object(Path, 'exists', return_value=False):
            cm = ContextManager("/tmp/test")
        
        assert hasattr(cm, 'context')
        assert hasattr(cm, 'projects')

    @patch('stack_cli.context.Path')
    def test_get_context_summary(self, mock_path):
        """Test getting context summary."""
        with patch.object(Path, 'exists', return_value=False):
            cm = ContextManager("/tmp/test")
        
        summary = cm.get_context_summary()
        
        assert "workspace" in summary
        assert "projects" in summary
        assert "session" in summary

    @patch('stack_cli.context.Path')
    def test_get_workspace_context(self, mock_path):
        """Test getting formatted workspace context."""
        with patch.object(Path, 'exists', return_value=False):
            cm = ContextManager("/tmp/test")
        
        context = cm.get_workspace_context()
        
        assert isinstance(context, str)
        assert "Workspace Context" in context

    @patch('pathlib.Path.exists')
    @patch('pathlib.Path.read_text')
    def test_search_memory(self, mock_read, mock_exists):
        """Test searching memory."""
        # Setup mock
        mock_exists.return_value = True
        
        def mock_file_exists(self):
            if str(self).endswith('MEMORY.md'):
                return True
            return False
        
        with patch.object(Path, 'exists', mock_file_exists):
            mock_read.return_value = "### key\nvalue"
            
            cm = ContextManager("/tmp/test")
            results = cm.search_memory("key")
            
            assert isinstance(results, list)

    @patch('pathlib.Path.write_text')
    def test_save_to_memory(self, mock_write):
        """Test saving to memory."""
        with patch.object(Path, 'exists', return_value=True):
            with patch('pathlib.Path.open', mock_open(read_data="")):
                cm = ContextManager("/tmp/test")
                cm.save_to_memory("test_key", "test_value")
        
        mock_write.assert_called()


class TestProjectAware:
    """Test ProjectAware mixin."""

    def test_project_aware_creation(self):
        """Test creating project aware."""
        pa = ProjectAware()
        
        assert pa is not None
        assert hasattr(pa, 'context_manager')

    def test_detect_project(self):
        """Test project detection."""
        pa = ProjectAware()
        
        # Should return None for non-existent path
        result = pa.detect_project("/nonexistent/path")
        
        assert result is None or isinstance(result, str)

    def test_get_project_context(self):
        """Test getting project context."""
        pa = ProjectAware()
        
        # Should return None for non-existent project
        result = pa.get_project_context("nonexistent_project")
        
        assert result is None or isinstance(result, ProjectContext)

    def test_format_context_for_prompt(self):
        """Test formatting context for prompt."""
        pa = ProjectAware()
        
        context = pa.format_context_for_prompt()
        
        assert isinstance(context, str)


class TestCreateContextManager:
    """Test create_context_manager factory."""

    @patch('stack_cli.context.ContextManager')
    def test_create_context_manager_default(self, mock_cm):
        """Test creating with defaults."""
        mock_cm.return_value = MagicMock()
        
        cm = create_context_manager()
        
        assert cm is not None

    @patch('stack_cli.context.ContextManager')
    def test_create_context_manager_custom(self, mock_cm):
        """Test creating with custom workspace."""
        mock_cm.return_value = MagicMock()
        
        cm = create_context_manager("/custom/path")
        
        assert cm is not None


if __name__ == "__main__":
    pytest.main([__file__, "-v"])