File size: 10,263 Bytes
8576f52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
"""Tests for JARVIS memory system — task queue, conversations, memories."""

import json
import os
import sqlite3
import tempfile
import unittest
from unittest.mock import patch

import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class TestMemoryTaskQueue(unittest.TestCase):
    """Test deferred task queue in Memory."""

    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.tmp.close()
        import memory
        self._orig_path = memory.DB_PATH
        memory.DB_PATH = self.tmp.name
        self.mem = memory.Memory()

    def tearDown(self):
        import memory
        memory.DB_PATH = self._orig_path
        os.unlink(self.tmp.name)

    def test_add_task(self):
        task_id = self.mem.add_task("Fix bug in server.py", "Handle 404 errors", "code", 2)
        self.assertIsInstance(task_id, int)
        self.assertGreater(task_id, 0)

    def test_get_pending_tasks(self):
        self.mem.add_task("Task A", priority=0)
        self.mem.add_task("Task B", priority=2)
        self.mem.add_task("Task C", priority=1)
        tasks = self.mem.get_pending_tasks()
        self.assertEqual(len(tasks), 3)
        # Should be ordered by priority DESC
        self.assertEqual(tasks[0]["title"], "Task B")
        self.assertEqual(tasks[1]["title"], "Task C")
        self.assertEqual(tasks[2]["title"], "Task A")

    def test_get_task(self):
        task_id = self.mem.add_task("Test task", "Description here", "general", 1)
        task = self.mem.get_task(task_id)
        self.assertIsNotNone(task)
        self.assertEqual(task["title"], "Test task")
        self.assertEqual(task["description"], "Description here")
        self.assertEqual(task["task_type"], "general")
        self.assertEqual(task["priority"], 1)
        self.assertEqual(task["status"], "pending")

    def test_get_nonexistent_task(self):
        task = self.mem.get_task(9999)
        self.assertIsNone(task)

    def test_update_task_status(self):
        task_id = self.mem.add_task("Status test")
        self.mem.update_task_status(task_id, "in_progress")
        task = self.mem.get_task(task_id)
        self.assertEqual(task["status"], "in_progress")

        self.mem.update_task_status(task_id, "completed")
        task = self.mem.get_task(task_id)
        self.assertEqual(task["status"], "completed")
        self.assertIsNotNone(task["completed_at"])

    def test_delete_task(self):
        task_id = self.mem.add_task("Delete me")
        self.mem.delete_task(task_id)
        task = self.mem.get_task(task_id)
        self.assertIsNone(task)

    def test_get_all_tasks_excludes_completed(self):
        id1 = self.mem.add_task("Active task")
        id2 = self.mem.add_task("Done task")
        self.mem.update_task_status(id2, "completed")
        tasks = self.mem.get_all_tasks(include_completed=False)
        self.assertEqual(len(tasks), 1)
        self.assertEqual(tasks[0]["title"], "Active task")

    def test_get_all_tasks_includes_completed(self):
        id1 = self.mem.add_task("Active task")
        id2 = self.mem.add_task("Done task")
        self.mem.update_task_status(id2, "completed")
        tasks = self.mem.get_all_tasks(include_completed=True)
        self.assertEqual(len(tasks), 2)

    def test_pending_tasks_summary(self):
        self.mem.add_task("Fix tests", "Unit tests failing", priority=2)
        self.mem.add_task("Update docs", priority=0)
        summary = self.mem.get_pending_tasks_summary()
        self.assertIn("2 pending task(s)", summary)
        self.assertIn("Fix tests", summary)
        self.assertIn("[HIGH]", summary)
        self.assertIn("Update docs", summary)

    def test_pending_tasks_summary_empty(self):
        summary = self.mem.get_pending_tasks_summary()
        self.assertEqual(summary, "")

    def test_task_metadata(self):
        meta = {"source": "copilot", "file": "server.py"}
        task_id = self.mem.add_task("Meta task", metadata=meta)
        task = self.mem.get_task(task_id)
        stored_meta = json.loads(task["metadata"])
        self.assertEqual(stored_meta["source"], "copilot")
        self.assertEqual(stored_meta["file"], "server.py")


class TestMemoryConversations(unittest.TestCase):
    """Test conversation and memory storage."""

    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.tmp.close()
        import memory
        self._orig_path = memory.DB_PATH
        memory.DB_PATH = self.tmp.name
        self.mem = memory.Memory()

    def tearDown(self):
        import memory
        memory.DB_PATH = self._orig_path
        os.unlink(self.tmp.name)

    def test_save_and_search_memory(self):
        self.mem.save_memory("pref", "language", "Python")
        results = self.mem.search_memories("Python")
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]["value"], "Python")

    def test_conversation_flow(self):
        conv_id = self.mem.create_conversation("Test Chat")
        self.mem.add_message(conv_id, "user", "Hello")
        self.mem.add_message(conv_id, "assistant", "Hi there!")
        messages = self.mem.get_messages(conv_id)
        self.assertEqual(len(messages), 2)
        self.assertEqual(messages[0]["role"], "user")
        self.assertEqual(messages[1]["role"], "assistant")

    def test_context_summary(self):
        self.mem.save_memory("fact", "name", "Tony Stark")
        summary = self.mem.get_context_summary()
        self.assertIn("Tony Stark", summary)
        self.assertIn("[LONG-TERM MEMORY]", summary)


class TestClapDetector(unittest.TestCase):
    """Test clap detector impulse classification."""

    def test_classify_impulse_fallback(self):
        """Test that classify_impulse works even without numpy (returns 'clap')."""
        from clap_detector import ClapDetector
        detector = ClapDetector()
        # Create silence data
        data = b'\x00\x00' * 1024
        # Should not crash
        result = detector._classify_impulse(data)
        self.assertIn(result, ["clap", "snap"])

    def test_is_impulse_below_threshold(self):
        from clap_detector import ClapDetector
        detector = ClapDetector()
        detector.ambient_level = 100
        # Silence should not be an impulse
        data = b'\x00\x00' * 1024
        self.assertFalse(detector._is_impulse(data))

    def test_get_amplitude_silence(self):
        from clap_detector import ClapDetector
        detector = ClapDetector()
        data = b'\x00\x00' * 1024
        amp = detector._get_amplitude(data)
        self.assertEqual(amp, 0.0)

    def test_suppress_unsuppress(self):
        from clap_detector import ClapDetector
        detector = ClapDetector()
        self.assertFalse(detector.suppressed)
        detector.suppress()
        self.assertTrue(detector.suppressed)



class TestBuiltinTools(unittest.TestCase):
    """Test builtin tool functions."""

    def test_add_task_tool(self):
        """Test the add_task tool function."""
        import tempfile
        tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        tmp.close()
        import memory
        orig = memory.DB_PATH
        memory.DB_PATH = tmp.name
        try:
            mem = memory.Memory()
            from tools.builtin import add_task
            result = add_task("Test task", "Description", "code", 1)
            self.assertIn("Task #", result)
            self.assertIn("Test task", result)
            self.assertIn("medium", result)
        finally:
            memory.DB_PATH = orig
            os.unlink(tmp.name)

    def test_list_tasks_empty(self):
        import tempfile
        tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        tmp.close()
        import memory
        orig = memory.DB_PATH
        memory.DB_PATH = tmp.name
        try:
            mem = memory.Memory()
            from tools.builtin import list_tasks
            result = list_tasks()
            self.assertIn("No tasks", result)
        finally:
            memory.DB_PATH = orig
            os.unlink(tmp.name)

    def test_complete_task_tool(self):
        import tempfile
        tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        tmp.close()
        import memory
        orig = memory.DB_PATH
        memory.DB_PATH = tmp.name
        try:
            mem = memory.Memory()
            task_id = mem.add_task("Complete me")
            from tools.builtin import complete_task
            result = complete_task(task_id)
            self.assertIn("completed", result)
            self.assertIn("Complete me", result)
        finally:
            memory.DB_PATH = orig
            os.unlink(tmp.name)

    def test_complete_nonexistent_task(self):
        import tempfile
        tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        tmp.close()
        import memory
        orig = memory.DB_PATH
        memory.DB_PATH = tmp.name
        try:
            mem = memory.Memory()
            from tools.builtin import complete_task
            result = complete_task(9999)
            self.assertIn("not found", result)
        finally:
            memory.DB_PATH = orig
            os.unlink(tmp.name)


class TestVSCodeTools(unittest.TestCase):
    """Test VS Code tool parameter validation (no actual execution)."""

    def test_vscode_tools_registered(self):
        from tools import TOOL_REGISTRY
        import tools.vscode_tools
        vscode_tools = [k for k in TOOL_REGISTRY if k.startswith("vscode_") or k.startswith("copilot_")]
        self.assertGreaterEqual(len(vscode_tools), 4)
        self.assertIn("vscode_open", TOOL_REGISTRY)
        self.assertIn("copilot_chat", TOOL_REGISTRY)
        self.assertIn("copilot_inline", TOOL_REGISTRY)

    def test_tool_registry_has_task_tools(self):
        from tools import TOOL_REGISTRY
        import tools.builtin
        self.assertIn("add_task", TOOL_REGISTRY)
        self.assertIn("list_tasks", TOOL_REGISTRY)
        self.assertIn("complete_task", TOOL_REGISTRY)
        self.assertIn("delete_task", TOOL_REGISTRY)
        self.assertIn("open_terminal", TOOL_REGISTRY)


if __name__ == "__main__":
    unittest.main()