jarviscore-framework 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tests/test_context.py ADDED
@@ -0,0 +1,467 @@
1
+ """
2
+ Tests for JarvisCore context module.
3
+
4
+ Tests MemoryAccessor, DependencyAccessor, and JarvisContext classes
5
+ which provide the orchestration primitives for Custom Profile agents.
6
+ """
7
+ import pytest
8
+ from jarviscore.context import (
9
+ JarvisContext,
10
+ MemoryAccessor,
11
+ DependencyAccessor,
12
+ create_context
13
+ )
14
+
15
+
16
+ class TestMemoryAccessor:
17
+ """Tests for MemoryAccessor class."""
18
+
19
+ def test_init_with_empty_memory(self):
20
+ """Test initialization with empty memory dict."""
21
+ memory = MemoryAccessor({}, "step1")
22
+ assert len(memory) == 0
23
+ assert memory.keys() == []
24
+
25
+ def test_init_with_existing_memory(self):
26
+ """Test initialization with pre-populated memory."""
27
+ data = {"step0": {"status": "success", "output": [1, 2, 3]}}
28
+ memory = MemoryAccessor(data, "step1")
29
+ assert len(memory) == 1
30
+ assert "step0" in memory.keys()
31
+
32
+ def test_get_extracts_output(self):
33
+ """Test that get() extracts 'output' from result dicts."""
34
+ data = {
35
+ "step0": {"status": "success", "output": {"result": "data"}}
36
+ }
37
+ memory = MemoryAccessor(data, "step1")
38
+
39
+ result = memory.get("step0")
40
+ assert result == {"result": "data"}
41
+
42
+ def test_get_returns_raw_value_if_no_output_key(self):
43
+ """Test get() returns raw value if no 'output' key."""
44
+ data = {"step0": "raw_string_value"}
45
+ memory = MemoryAccessor(data, "step1")
46
+
47
+ result = memory.get("step0")
48
+ assert result == "raw_string_value"
49
+
50
+ def test_get_returns_default_for_missing_key(self):
51
+ """Test get() returns default for missing key."""
52
+ memory = MemoryAccessor({}, "step1")
53
+
54
+ assert memory.get("missing") is None
55
+ assert memory.get("missing", default="fallback") == "fallback"
56
+ assert memory.get("missing", default=[]) == []
57
+
58
+ def test_get_raw_returns_full_dict(self):
59
+ """Test get_raw() returns full result dict."""
60
+ data = {
61
+ "step0": {"status": "success", "output": "data", "agent": "agent1"}
62
+ }
63
+ memory = MemoryAccessor(data, "step1")
64
+
65
+ result = memory.get_raw("step0")
66
+ assert result == {"status": "success", "output": "data", "agent": "agent1"}
67
+
68
+ def test_put_stores_value(self):
69
+ """Test put() stores value in memory."""
70
+ data = {}
71
+ memory = MemoryAccessor(data, "step1")
72
+
73
+ memory.put("intermediate", {"processed": True})
74
+
75
+ assert "intermediate" in data
76
+ assert data["intermediate"] == {"processed": True}
77
+
78
+ def test_has_returns_true_for_existing_key(self):
79
+ """Test has() returns True for existing key."""
80
+ data = {"step0": "value"}
81
+ memory = MemoryAccessor(data, "step1")
82
+
83
+ assert memory.has("step0") is True
84
+ assert memory.has("missing") is False
85
+
86
+ def test_all_extracts_outputs(self):
87
+ """Test all() extracts outputs from all results."""
88
+ data = {
89
+ "step0": {"status": "success", "output": [1, 2, 3]},
90
+ "step1": {"status": "success", "output": {"key": "value"}},
91
+ "raw": "raw_value"
92
+ }
93
+ memory = MemoryAccessor(data, "step2")
94
+
95
+ result = memory.all()
96
+
97
+ assert result["step0"] == [1, 2, 3]
98
+ assert result["step1"] == {"key": "value"}
99
+ assert result["raw"] == "raw_value"
100
+
101
+ def test_keys_returns_all_keys(self):
102
+ """Test keys() returns all memory keys."""
103
+ data = {"step0": "a", "step1": "b", "step2": "c"}
104
+ memory = MemoryAccessor(data, "step3")
105
+
106
+ keys = memory.keys()
107
+
108
+ assert set(keys) == {"step0", "step1", "step2"}
109
+
110
+ def test_contains_operator(self):
111
+ """Test 'in' operator support."""
112
+ data = {"step0": "value"}
113
+ memory = MemoryAccessor(data, "step1")
114
+
115
+ assert "step0" in memory
116
+ assert "missing" not in memory
117
+
118
+ def test_getitem_operator(self):
119
+ """Test dict-style access with []."""
120
+ data = {"step0": {"status": "success", "output": "data"}}
121
+ memory = MemoryAccessor(data, "step1")
122
+
123
+ assert memory["step0"] == "data"
124
+
125
+ def test_setitem_operator(self):
126
+ """Test dict-style assignment with []."""
127
+ data = {}
128
+ memory = MemoryAccessor(data, "step1")
129
+
130
+ memory["key"] = "value"
131
+
132
+ assert data["key"] == "value"
133
+
134
+ def test_len(self):
135
+ """Test len() returns number of items."""
136
+ data = {"a": 1, "b": 2, "c": 3}
137
+ memory = MemoryAccessor(data, "step1")
138
+
139
+ assert len(memory) == 3
140
+
141
+ def test_repr(self):
142
+ """Test string representation."""
143
+ data = {"step0": "a", "step1": "b"}
144
+ memory = MemoryAccessor(data, "step2")
145
+
146
+ repr_str = repr(memory)
147
+
148
+ assert "MemoryAccessor" in repr_str
149
+ assert "step0" in repr_str or "keys=" in repr_str
150
+
151
+
152
+ class TestDependencyAccessor:
153
+ """Tests for DependencyAccessor class."""
154
+
155
+ def test_init_without_manager(self):
156
+ """Test initialization without dependency manager."""
157
+ memory = {"step0": "value"}
158
+ deps = DependencyAccessor(None, memory)
159
+
160
+ assert deps._manager is None
161
+ assert deps._memory == memory
162
+
163
+ def test_is_ready_returns_true_for_existing_step(self):
164
+ """Test is_ready() returns True for step in memory."""
165
+ memory = {"step0": {"output": "data"}}
166
+ deps = DependencyAccessor(None, memory)
167
+
168
+ assert deps.is_ready("step0") is True
169
+ assert deps.is_ready("missing") is False
170
+
171
+ def test_all_ready_returns_true_when_all_present(self):
172
+ """Test all_ready() returns True when all steps present."""
173
+ memory = {"step0": "a", "step1": "b", "step2": "c"}
174
+ deps = DependencyAccessor(None, memory)
175
+
176
+ assert deps.all_ready(["step0", "step1"]) is True
177
+ assert deps.all_ready(["step0", "step1", "step2"]) is True
178
+ assert deps.all_ready(["step0", "missing"]) is False
179
+
180
+ def test_any_ready_returns_true_when_any_present(self):
181
+ """Test any_ready() returns True when any step present."""
182
+ memory = {"step0": "a"}
183
+ deps = DependencyAccessor(None, memory)
184
+
185
+ assert deps.any_ready(["step0", "missing"]) is True
186
+ assert deps.any_ready(["missing1", "missing2"]) is False
187
+
188
+ def test_check_without_manager(self):
189
+ """Test check() works without manager (fallback mode)."""
190
+ memory = {"step0": "a", "step1": "b"}
191
+ deps = DependencyAccessor(None, memory)
192
+
193
+ ready, missing = deps.check(["step0", "step1"])
194
+ assert ready is True
195
+ assert missing == []
196
+
197
+ ready, missing = deps.check(["step0", "step2"])
198
+ assert ready is False
199
+ assert missing == ["step2"]
200
+
201
+ def test_simple_wait_without_manager(self):
202
+ """Test _simple_wait() extracts outputs."""
203
+ memory = {
204
+ "step0": {"status": "success", "output": [1, 2, 3]},
205
+ "step1": "raw_value"
206
+ }
207
+ deps = DependencyAccessor(None, memory)
208
+
209
+ result = deps._simple_wait(["step0", "step1"])
210
+
211
+ assert result["step0"] == [1, 2, 3]
212
+ assert result["step1"] == "raw_value"
213
+
214
+ def test_repr(self):
215
+ """Test string representation."""
216
+ memory = {"step0": "a", "step1": "b"}
217
+ deps = DependencyAccessor(None, memory)
218
+
219
+ repr_str = repr(deps)
220
+
221
+ assert "DependencyAccessor" in repr_str
222
+
223
+
224
+ class TestJarvisContext:
225
+ """Tests for JarvisContext class."""
226
+
227
+ @pytest.fixture
228
+ def sample_memory(self):
229
+ """Sample memory dict for tests."""
230
+ return {
231
+ "step0": {"status": "success", "output": {"data": [1, 2, 3]}},
232
+ "step1": {"status": "success", "output": "processed"}
233
+ }
234
+
235
+ @pytest.fixture
236
+ def sample_context(self, sample_memory):
237
+ """Create a sample JarvisContext."""
238
+ memory_accessor = MemoryAccessor(sample_memory, "step2")
239
+ dep_accessor = DependencyAccessor(None, sample_memory)
240
+
241
+ return JarvisContext(
242
+ workflow_id="test-workflow",
243
+ step_id="step2",
244
+ task="Process data",
245
+ params={"threshold": 0.5, "mode": "fast"},
246
+ memory=memory_accessor,
247
+ deps=dep_accessor
248
+ )
249
+
250
+ def test_context_attributes(self, sample_context):
251
+ """Test context has correct attributes."""
252
+ assert sample_context.workflow_id == "test-workflow"
253
+ assert sample_context.step_id == "step2"
254
+ assert sample_context.task == "Process data"
255
+ assert sample_context.params == {"threshold": 0.5, "mode": "fast"}
256
+
257
+ def test_previous_gets_step_output(self, sample_context):
258
+ """Test previous() retrieves step output."""
259
+ result = sample_context.previous("step0")
260
+ assert result == {"data": [1, 2, 3]}
261
+
262
+ result = sample_context.previous("step1")
263
+ assert result == "processed"
264
+
265
+ def test_previous_returns_default_for_missing(self, sample_context):
266
+ """Test previous() returns default for missing step."""
267
+ result = sample_context.previous("missing")
268
+ assert result is None
269
+
270
+ result = sample_context.previous("missing", default={})
271
+ assert result == {}
272
+
273
+ def test_all_previous_gets_all_outputs(self, sample_context):
274
+ """Test all_previous() retrieves all outputs."""
275
+ result = sample_context.all_previous()
276
+
277
+ assert "step0" in result
278
+ assert "step1" in result
279
+ assert result["step0"] == {"data": [1, 2, 3]}
280
+ assert result["step1"] == "processed"
281
+
282
+ def test_previous_results_property(self, sample_context):
283
+ """Test previous_results property is alias for all_previous()."""
284
+ result = sample_context.previous_results
285
+ assert result == sample_context.all_previous()
286
+
287
+ def test_has_previous(self, sample_context):
288
+ """Test has_previous() checks step existence."""
289
+ assert sample_context.has_previous("step0") is True
290
+ assert sample_context.has_previous("step1") is True
291
+ assert sample_context.has_previous("missing") is False
292
+
293
+ def test_get_param(self, sample_context):
294
+ """Test get_param() retrieves task parameters."""
295
+ assert sample_context.get_param("threshold") == 0.5
296
+ assert sample_context.get_param("mode") == "fast"
297
+ assert sample_context.get_param("missing") is None
298
+ assert sample_context.get_param("missing", default=1.0) == 1.0
299
+
300
+ def test_memory_accessor_available(self, sample_context):
301
+ """Test memory accessor is available on context."""
302
+ assert sample_context.memory is not None
303
+ assert isinstance(sample_context.memory, MemoryAccessor)
304
+ assert sample_context.memory.get("step0") == {"data": [1, 2, 3]}
305
+
306
+ def test_deps_accessor_available(self, sample_context):
307
+ """Test dependency accessor is available on context."""
308
+ assert sample_context.deps is not None
309
+ assert isinstance(sample_context.deps, DependencyAccessor)
310
+ assert sample_context.deps.is_ready("step0") is True
311
+
312
+ def test_context_with_none_memory(self):
313
+ """Test context handles None memory gracefully."""
314
+ ctx = JarvisContext(
315
+ workflow_id="test",
316
+ step_id="step0",
317
+ task="test",
318
+ params={},
319
+ memory=None,
320
+ deps=None
321
+ )
322
+
323
+ assert ctx.previous("any") is None
324
+ assert ctx.all_previous() == {}
325
+ assert ctx.has_previous("any") is False
326
+
327
+ def test_repr(self, sample_context):
328
+ """Test string representation."""
329
+ repr_str = repr(sample_context)
330
+
331
+ assert "JarvisContext" in repr_str
332
+ assert "test-workflow" in repr_str
333
+ assert "step2" in repr_str
334
+
335
+
336
+ class TestCreateContext:
337
+ """Tests for create_context factory function."""
338
+
339
+ def test_create_context_basic(self):
340
+ """Test creating context with basic parameters."""
341
+ memory_dict = {"step0": {"output": "data"}}
342
+
343
+ ctx = create_context(
344
+ workflow_id="workflow-1",
345
+ step_id="step1",
346
+ task="Process",
347
+ params={"key": "value"},
348
+ memory_dict=memory_dict,
349
+ dependency_manager=None
350
+ )
351
+
352
+ assert ctx.workflow_id == "workflow-1"
353
+ assert ctx.step_id == "step1"
354
+ assert ctx.task == "Process"
355
+ assert ctx.params == {"key": "value"}
356
+ assert ctx.memory is not None
357
+ assert ctx.deps is not None
358
+
359
+ def test_create_context_memory_works(self):
360
+ """Test that created context's memory accessor works."""
361
+ memory_dict = {"step0": {"output": [1, 2, 3]}}
362
+
363
+ ctx = create_context(
364
+ workflow_id="w1",
365
+ step_id="step1",
366
+ task="test",
367
+ params={},
368
+ memory_dict=memory_dict
369
+ )
370
+
371
+ assert ctx.previous("step0") == [1, 2, 3]
372
+
373
+ def test_create_context_deps_works(self):
374
+ """Test that created context's deps accessor works."""
375
+ memory_dict = {"step0": "data", "step1": "more"}
376
+
377
+ ctx = create_context(
378
+ workflow_id="w1",
379
+ step_id="step2",
380
+ task="test",
381
+ params={},
382
+ memory_dict=memory_dict
383
+ )
384
+
385
+ assert ctx.deps.is_ready("step0") is True
386
+ assert ctx.deps.all_ready(["step0", "step1"]) is True
387
+
388
+ def test_create_context_empty_params(self):
389
+ """Test creating context with empty params."""
390
+ ctx = create_context(
391
+ workflow_id="w1",
392
+ step_id="step0",
393
+ task="test",
394
+ params={},
395
+ memory_dict={}
396
+ )
397
+
398
+ assert ctx.params == {}
399
+ assert ctx.get_param("any") is None
400
+
401
+
402
+ class TestMemoryAccessorEdgeCases:
403
+ """Edge case tests for MemoryAccessor."""
404
+
405
+ def test_nested_output_extraction(self):
406
+ """Test extraction with nested output structure."""
407
+ data = {
408
+ "step0": {
409
+ "status": "success",
410
+ "output": {
411
+ "nested": {
412
+ "deep": {"value": 42}
413
+ }
414
+ }
415
+ }
416
+ }
417
+ memory = MemoryAccessor(data, "step1")
418
+
419
+ result = memory.get("step0")
420
+ assert result["nested"]["deep"]["value"] == 42
421
+
422
+ def test_list_output_extraction(self):
423
+ """Test extraction when output is a list."""
424
+ data = {
425
+ "step0": {"status": "success", "output": [1, 2, 3, 4, 5]}
426
+ }
427
+ memory = MemoryAccessor(data, "step1")
428
+
429
+ result = memory.get("step0")
430
+ assert result == [1, 2, 3, 4, 5]
431
+
432
+ def test_none_output_extraction(self):
433
+ """Test extraction when output is None."""
434
+ data = {
435
+ "step0": {"status": "success", "output": None}
436
+ }
437
+ memory = MemoryAccessor(data, "step1")
438
+
439
+ result = memory.get("step0")
440
+ assert result is None
441
+
442
+ def test_empty_dict_output(self):
443
+ """Test extraction when output is empty dict."""
444
+ data = {
445
+ "step0": {"status": "success", "output": {}}
446
+ }
447
+ memory = MemoryAccessor(data, "step1")
448
+
449
+ result = memory.get("step0")
450
+ assert result == {}
451
+
452
+ def test_mutation_through_accessor(self):
453
+ """Test that mutations through accessor affect original dict."""
454
+ data = {}
455
+ memory = MemoryAccessor(data, "step1")
456
+
457
+ memory.put("key", {"nested": "value"})
458
+
459
+ # Original dict should be mutated
460
+ assert data["key"] == {"nested": "value"}
461
+
462
+ # Modify through get and verify
463
+ result = memory.get("key")
464
+ result["new_key"] = "new_value"
465
+
466
+ # Original should reflect change (same reference)
467
+ assert data["key"]["new_key"] == "new_value"