aiagents4pharma 1.30.1__py3-none-any.whl → 1.30.2__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.
- aiagents4pharma/talk2scholars/agents/zotero_agent.py +9 -7
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +9 -15
- aiagents4pharma/talk2scholars/configs/tools/zotero_write/default.yaml +55 -0
- aiagents4pharma/talk2scholars/state/state_talk2scholars.py +3 -0
- aiagents4pharma/talk2scholars/tests/test_routing_logic.py +1 -2
- aiagents4pharma/talk2scholars/tests/test_zotero_agent.py +3 -2
- aiagents4pharma/talk2scholars/tests/test_zotero_human_in_the_loop.py +273 -0
- aiagents4pharma/talk2scholars/tests/test_zotero_path.py +419 -1
- aiagents4pharma/talk2scholars/tests/test_zotero_read.py +9 -9
- aiagents4pharma/talk2scholars/tests/test_zotero_write.py +123 -588
- aiagents4pharma/talk2scholars/tools/zotero/__init__.py +1 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +125 -0
- aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +2 -4
- aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +198 -0
- aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +47 -111
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.2.dist-info}/METADATA +3 -2
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.2.dist-info}/RECORD +20 -17
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.2.dist-info}/WHEEL +1 -1
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.2.dist-info/licenses}/LICENSE +0 -0
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.2.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,20 @@ Unit tests for Zotero path utility in zotero_path.py.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import unittest
|
6
|
-
from unittest.mock import MagicMock
|
6
|
+
from unittest.mock import MagicMock, patch
|
7
|
+
import pytest
|
7
8
|
from aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path import (
|
9
|
+
fetch_papers_for_save,
|
10
|
+
find_or_create_collection,
|
11
|
+
get_all_collection_paths,
|
8
12
|
get_item_collections,
|
9
13
|
)
|
14
|
+
from aiagents4pharma.talk2scholars.tools.zotero.zotero_read import (
|
15
|
+
zotero_read,
|
16
|
+
)
|
17
|
+
from aiagents4pharma.talk2scholars.tools.zotero.zotero_write import (
|
18
|
+
zotero_write,
|
19
|
+
)
|
10
20
|
|
11
21
|
|
12
22
|
class TestGetItemCollections(unittest.TestCase):
|
@@ -55,3 +65,411 @@ class TestGetItemCollections(unittest.TestCase):
|
|
55
65
|
|
56
66
|
result = get_item_collections(fake_zot)
|
57
67
|
self.assertEqual(result, expected_mapping)
|
68
|
+
|
69
|
+
|
70
|
+
class TestFindOrCreateCollectionExtra(unittest.TestCase):
|
71
|
+
"""Extra tests for the find_or_create_collection function."""
|
72
|
+
|
73
|
+
def setUp(self):
|
74
|
+
"""Set up a fake Zotero client with some default collections."""
|
75
|
+
# Set up a fake Zotero client with some default collections.
|
76
|
+
self.fake_zot = MagicMock()
|
77
|
+
self.fake_zot.collections.return_value = [
|
78
|
+
{"key": "parent1", "data": {"name": "Parent", "parentCollection": None}},
|
79
|
+
{"key": "child1", "data": {"name": "Child", "parentCollection": "parent1"}},
|
80
|
+
]
|
81
|
+
|
82
|
+
def test_empty_path(self):
|
83
|
+
"""Test that an empty path returns None."""
|
84
|
+
result = find_or_create_collection(self.fake_zot, "", create_missing=False)
|
85
|
+
self.assertIsNone(result)
|
86
|
+
|
87
|
+
def test_create_collection_with_success_key(self):
|
88
|
+
"""
|
89
|
+
Test that when create_missing is True and the response contains a "success" key,
|
90
|
+
the function returns the new collection key.
|
91
|
+
"""
|
92
|
+
# Simulate no existing collections (so direct match fails)
|
93
|
+
self.fake_zot.collections.return_value = []
|
94
|
+
# Simulate create_collection returning a dict with a "success" key.
|
95
|
+
self.fake_zot.create_collection.return_value = {
|
96
|
+
"success": {"0": "new_key_success"}
|
97
|
+
}
|
98
|
+
result = find_or_create_collection(
|
99
|
+
self.fake_zot, "/NewCollection", create_missing=True
|
100
|
+
)
|
101
|
+
self.assertEqual(result, "new_key_success")
|
102
|
+
# Verify payload formatting: for a simple (non-nested) path, no parentCollection.
|
103
|
+
args, _ = self.fake_zot.create_collection.call_args
|
104
|
+
payload = args[0]
|
105
|
+
self.assertEqual(payload["name"], "newcollection")
|
106
|
+
self.assertNotIn("parentCollection", payload)
|
107
|
+
|
108
|
+
def test_create_collection_with_successful_key(self):
|
109
|
+
"""
|
110
|
+
Test that when create_missing is True and the response contains a "successful" key,
|
111
|
+
the function returns the new collection key.
|
112
|
+
"""
|
113
|
+
self.fake_zot.collections.return_value = []
|
114
|
+
self.fake_zot.create_collection.return_value = {
|
115
|
+
"successful": {"0": {"data": {"key": "new_key_successful"}}}
|
116
|
+
}
|
117
|
+
result = find_or_create_collection(
|
118
|
+
self.fake_zot, "/NewCollection", create_missing=True
|
119
|
+
)
|
120
|
+
self.assertEqual(result, "new_key_successful")
|
121
|
+
|
122
|
+
def test_create_collection_exception(self):
|
123
|
+
"""
|
124
|
+
Test that if create_collection raises an exception,
|
125
|
+
the function logs the error and returns None.
|
126
|
+
"""
|
127
|
+
self.fake_zot.collections.return_value = []
|
128
|
+
self.fake_zot.create_collection.side_effect = Exception("Creation error")
|
129
|
+
result = find_or_create_collection(
|
130
|
+
self.fake_zot, "/NewCollection", create_missing=True
|
131
|
+
)
|
132
|
+
self.assertIsNone(result)
|
133
|
+
|
134
|
+
|
135
|
+
class TestZoteroPath:
|
136
|
+
"""Tests for the zotero_path utility functions."""
|
137
|
+
|
138
|
+
def test_fetch_papers_for_save_no_papers(self):
|
139
|
+
"""Test that fetch_papers_for_save returns None when no papers are available."""
|
140
|
+
# Empty state
|
141
|
+
state = {}
|
142
|
+
assert fetch_papers_for_save(state) is None
|
143
|
+
|
144
|
+
# State with empty last_displayed_papers
|
145
|
+
state = {"last_displayed_papers": ""}
|
146
|
+
assert fetch_papers_for_save(state) is None
|
147
|
+
|
148
|
+
# State with last_displayed_papers pointing to non-existent key
|
149
|
+
state = {"last_displayed_papers": "nonexistent_key"}
|
150
|
+
assert fetch_papers_for_save(state) is None
|
151
|
+
|
152
|
+
def test_fetch_papers_for_save_with_papers(self):
|
153
|
+
"""Test that fetch_papers_for_save correctly retrieves papers from state."""
|
154
|
+
# State with direct papers
|
155
|
+
sample_papers = {"paper1": {"Title": "Test Paper"}}
|
156
|
+
state = {"last_displayed_papers": sample_papers}
|
157
|
+
assert fetch_papers_for_save(state) == sample_papers
|
158
|
+
|
159
|
+
# State with papers referenced by key
|
160
|
+
state = {"last_displayed_papers": "zotero_read", "zotero_read": sample_papers}
|
161
|
+
assert fetch_papers_for_save(state) == sample_papers
|
162
|
+
|
163
|
+
@patch("pyzotero.zotero.Zotero")
|
164
|
+
def test_find_or_create_collection_exact_match(self, mock_zotero):
|
165
|
+
"""Test that find_or_create_collection correctly finds an exact match."""
|
166
|
+
# Setup mock
|
167
|
+
mock_zot = MagicMock()
|
168
|
+
mock_zotero.return_value = mock_zot
|
169
|
+
|
170
|
+
# Setup collections
|
171
|
+
collections = [
|
172
|
+
{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
|
173
|
+
{
|
174
|
+
"key": "def456",
|
175
|
+
"data": {"name": "Curiosity1", "parentCollection": "abc123"},
|
176
|
+
},
|
177
|
+
{"key": "ghi789", "data": {"name": "Random", "parentCollection": None}},
|
178
|
+
{"key": "rad123", "data": {"name": "radiation", "parentCollection": None}},
|
179
|
+
]
|
180
|
+
mock_zot.collections.return_value = collections
|
181
|
+
|
182
|
+
# Test finding "Curiosity"
|
183
|
+
result = find_or_create_collection(mock_zot, "/Curiosity")
|
184
|
+
assert result == "abc123"
|
185
|
+
|
186
|
+
# Test finding with different case
|
187
|
+
result = find_or_create_collection(mock_zot, "/curiosity")
|
188
|
+
assert result == "abc123"
|
189
|
+
|
190
|
+
# Test finding "radiation" - direct match
|
191
|
+
result = find_or_create_collection(mock_zot, "/radiation")
|
192
|
+
assert result == "rad123"
|
193
|
+
|
194
|
+
# Test finding without leading slash
|
195
|
+
result = find_or_create_collection(mock_zot, "radiation")
|
196
|
+
assert result == "rad123"
|
197
|
+
|
198
|
+
@patch("pyzotero.zotero.Zotero")
|
199
|
+
def test_find_or_create_collection_no_match(self, mock_zotero):
|
200
|
+
"""Test that find_or_create_collection returns None for non-existent collections."""
|
201
|
+
# Setup mock
|
202
|
+
mock_zot = MagicMock()
|
203
|
+
mock_zotero.return_value = mock_zot
|
204
|
+
|
205
|
+
# Setup collections
|
206
|
+
collections = [
|
207
|
+
{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
|
208
|
+
{
|
209
|
+
"key": "def456",
|
210
|
+
"data": {"name": "Curiosity1", "parentCollection": "abc123"},
|
211
|
+
},
|
212
|
+
]
|
213
|
+
mock_zot.collections.return_value = collections
|
214
|
+
|
215
|
+
# Test finding non-existent "Curiosity2"
|
216
|
+
result = find_or_create_collection(mock_zot, "/Curiosity2")
|
217
|
+
assert result is None
|
218
|
+
|
219
|
+
# Test finding non-existent nested path
|
220
|
+
result = find_or_create_collection(mock_zot, "/Curiosity/Curiosity2")
|
221
|
+
assert result is None
|
222
|
+
|
223
|
+
@patch("pyzotero.zotero.Zotero")
|
224
|
+
def test_find_or_create_collection_with_creation(self, mock_zotero):
|
225
|
+
"""Test that find_or_create_collection creates collections when requested."""
|
226
|
+
# Setup mock
|
227
|
+
mock_zot = MagicMock()
|
228
|
+
mock_zotero.return_value = mock_zot
|
229
|
+
|
230
|
+
# Setup collections
|
231
|
+
collections = [
|
232
|
+
{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}}
|
233
|
+
]
|
234
|
+
mock_zot.collections.return_value = collections
|
235
|
+
|
236
|
+
# Setup create_collection response
|
237
|
+
mock_zot.create_collection.return_value = {
|
238
|
+
"successful": {"0": {"data": {"key": "new_key"}}}
|
239
|
+
}
|
240
|
+
|
241
|
+
# Test creating "Curiosity2" - note we're expecting lowercase in the call
|
242
|
+
result = find_or_create_collection(mock_zot, "/Curiosity2", create_missing=True)
|
243
|
+
assert result == "new_key"
|
244
|
+
# Use case-insensitive check for the collection name
|
245
|
+
mock_zot.create_collection.assert_called_once()
|
246
|
+
call_args = mock_zot.create_collection.call_args[0][0]
|
247
|
+
assert "name" in call_args
|
248
|
+
assert call_args["name"].lower() == "curiosity2"
|
249
|
+
|
250
|
+
# Test creating nested "Curiosity/Curiosity2"
|
251
|
+
mock_zot.create_collection.reset_mock()
|
252
|
+
result = find_or_create_collection(
|
253
|
+
mock_zot, "/Curiosity/Curiosity2", create_missing=True
|
254
|
+
)
|
255
|
+
assert result == "new_key"
|
256
|
+
# Check that the call includes parentCollection
|
257
|
+
mock_zot.create_collection.assert_called_once()
|
258
|
+
call_args = mock_zot.create_collection.call_args[0][0]
|
259
|
+
assert "name" in call_args
|
260
|
+
assert "parentCollection" in call_args
|
261
|
+
assert call_args["name"].lower() == "curiosity2"
|
262
|
+
assert call_args["parentCollection"] == "abc123"
|
263
|
+
|
264
|
+
@patch("pyzotero.zotero.Zotero")
|
265
|
+
def test_get_all_collection_paths(self, mock_zotero):
|
266
|
+
"""Test that get_all_collection_paths returns correct paths."""
|
267
|
+
# Setup mock
|
268
|
+
mock_zot = MagicMock()
|
269
|
+
mock_zotero.return_value = mock_zot
|
270
|
+
|
271
|
+
# Setup collections
|
272
|
+
collections = [
|
273
|
+
{"key": "abc123", "data": {"name": "Curiosity", "parentCollection": None}},
|
274
|
+
{
|
275
|
+
"key": "def456",
|
276
|
+
"data": {"name": "Curiosity1", "parentCollection": "abc123"},
|
277
|
+
},
|
278
|
+
{"key": "ghi789", "data": {"name": "Random", "parentCollection": None}},
|
279
|
+
]
|
280
|
+
mock_zot.collections.return_value = collections
|
281
|
+
|
282
|
+
# Test getting all paths
|
283
|
+
result = get_all_collection_paths(mock_zot)
|
284
|
+
assert "/Curiosity" in result
|
285
|
+
assert "/Random" in result
|
286
|
+
assert "/Curiosity/Curiosity1" in result
|
287
|
+
|
288
|
+
|
289
|
+
class TestZoteroWrite:
|
290
|
+
"""Integration tests for zotero_write.py."""
|
291
|
+
|
292
|
+
@pytest.fixture
|
293
|
+
def mock_hydra(self):
|
294
|
+
"""Fixture to mock hydra configuration."""
|
295
|
+
with patch("hydra.compose") as mock_compose:
|
296
|
+
cfg = MagicMock()
|
297
|
+
cfg.tools.zotero_write.user_id = "test_user"
|
298
|
+
cfg.tools.zotero_write.library_type = "user"
|
299
|
+
cfg.tools.zotero_write.api_key = "test_key"
|
300
|
+
cfg.tools.zotero_write.zotero = MagicMock()
|
301
|
+
cfg.tools.zotero_write.zotero.max_limit = 50
|
302
|
+
mock_compose.return_value = cfg
|
303
|
+
yield cfg
|
304
|
+
|
305
|
+
@pytest.fixture
|
306
|
+
def mock_zotero(self):
|
307
|
+
"""Fixture to mock Zotero client."""
|
308
|
+
with patch("pyzotero.zotero.Zotero") as mock_zot_class:
|
309
|
+
mock_zot = MagicMock()
|
310
|
+
mock_zot_class.return_value = mock_zot
|
311
|
+
yield mock_zot
|
312
|
+
|
313
|
+
@patch(
|
314
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.fetch_papers_for_save"
|
315
|
+
)
|
316
|
+
def test_zotero_write_no_papers(self, mock_fetch):
|
317
|
+
"""When no papers exist (even after approval), the function raises a ValueError."""
|
318
|
+
mock_fetch.return_value = None
|
319
|
+
|
320
|
+
state = {
|
321
|
+
"zotero_write_approval_status": {
|
322
|
+
"approved": True,
|
323
|
+
"collection_path": "/Curiosity",
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
with pytest.raises(ValueError) as excinfo:
|
328
|
+
zotero_write.run(
|
329
|
+
{
|
330
|
+
"tool_call_id": "test_id",
|
331
|
+
"collection_path": "/Curiosity",
|
332
|
+
"state": state,
|
333
|
+
}
|
334
|
+
)
|
335
|
+
assert "No fetched papers were found to save" in str(excinfo.value)
|
336
|
+
|
337
|
+
@patch(
|
338
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.fetch_papers_for_save"
|
339
|
+
)
|
340
|
+
@patch(
|
341
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.find_or_create_collection"
|
342
|
+
)
|
343
|
+
def test_zotero_write_invalid_collection(self, mock_find, mock_fetch, mock_zotero):
|
344
|
+
"""Saving to a nonexistent Zotero collection returns an error Command."""
|
345
|
+
sample = {"paper1": {"Title": "Test Paper"}}
|
346
|
+
mock_fetch.return_value = sample
|
347
|
+
mock_find.return_value = None
|
348
|
+
mock_zotero.collections.return_value = [
|
349
|
+
{"key": "k1", "data": {"name": "Curiosity"}},
|
350
|
+
{"key": "k2", "data": {"name": "Random"}},
|
351
|
+
]
|
352
|
+
|
353
|
+
state = {
|
354
|
+
"zotero_write_approval_status": {
|
355
|
+
"approved": True,
|
356
|
+
"collection_path": "/NonExistent",
|
357
|
+
},
|
358
|
+
"last_displayed_papers": "papers",
|
359
|
+
"papers": sample,
|
360
|
+
}
|
361
|
+
|
362
|
+
result = zotero_write.run(
|
363
|
+
{
|
364
|
+
"tool_call_id": "test_id",
|
365
|
+
"collection_path": "/NonExistent",
|
366
|
+
"state": state,
|
367
|
+
}
|
368
|
+
)
|
369
|
+
|
370
|
+
msg = result.update["messages"][0].content
|
371
|
+
assert "does not exist in Zotero" in msg
|
372
|
+
assert "Curiosity, Random" in msg
|
373
|
+
|
374
|
+
@patch(
|
375
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.fetch_papers_for_save"
|
376
|
+
)
|
377
|
+
@patch(
|
378
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.find_or_create_collection"
|
379
|
+
)
|
380
|
+
def test_zotero_write_success(self, mock_find, mock_fetch, mock_hydra, mock_zotero):
|
381
|
+
"""A valid approved save returns a success Command with summary."""
|
382
|
+
sample = {"paper1": {"Title": "Test Paper", "Authors": ["Test Author"]}}
|
383
|
+
mock_fetch.return_value = sample
|
384
|
+
mock_find.return_value = "abc123"
|
385
|
+
mock_zotero.collections.return_value = [
|
386
|
+
{"key": "abc123", "data": {"name": "radiation"}}
|
387
|
+
]
|
388
|
+
mock_zotero.create_items.return_value = {
|
389
|
+
"successful": {"0": {"key": "item123"}}
|
390
|
+
}
|
391
|
+
mock_hydra.tools.zotero_write.zotero.max_limit = 50
|
392
|
+
|
393
|
+
state = {
|
394
|
+
"zotero_write_approval_status": {
|
395
|
+
"approved": True,
|
396
|
+
"collection_path": "/radiation",
|
397
|
+
},
|
398
|
+
"last_displayed_papers": "papers",
|
399
|
+
"papers": sample,
|
400
|
+
}
|
401
|
+
|
402
|
+
result = zotero_write.run(
|
403
|
+
{
|
404
|
+
"tool_call_id": "test_id",
|
405
|
+
"collection_path": "/radiation",
|
406
|
+
"state": state,
|
407
|
+
}
|
408
|
+
)
|
409
|
+
|
410
|
+
msg = result.update["messages"][0].content
|
411
|
+
assert "Save was successful" in msg
|
412
|
+
assert "radiation" in msg
|
413
|
+
|
414
|
+
|
415
|
+
class TestZoteroRead:
|
416
|
+
"""Integration tests for zotero_read.py."""
|
417
|
+
|
418
|
+
@pytest.fixture
|
419
|
+
def mock_hydra(self):
|
420
|
+
"""Fixture to mock hydra configuration."""
|
421
|
+
with patch("hydra.initialize"), patch("hydra.compose") as mock_compose:
|
422
|
+
cfg = MagicMock()
|
423
|
+
cfg.tools.zotero_read.user_id = "test_user"
|
424
|
+
cfg.tools.zotero_read.library_type = "user"
|
425
|
+
cfg.tools.zotero_read.api_key = "test_key"
|
426
|
+
cfg.tools.zotero_read.zotero = MagicMock()
|
427
|
+
cfg.tools.zotero_read.zotero.max_limit = 50
|
428
|
+
cfg.tools.zotero_read.zotero.filter_item_types = [
|
429
|
+
"journalArticle",
|
430
|
+
"conferencePaper",
|
431
|
+
]
|
432
|
+
mock_compose.return_value = cfg
|
433
|
+
yield cfg
|
434
|
+
|
435
|
+
@pytest.fixture
|
436
|
+
def mock_zotero(self):
|
437
|
+
"""Fixture to mock Zotero client."""
|
438
|
+
with patch("pyzotero.zotero.Zotero") as mock_zot_class:
|
439
|
+
mock_zot = MagicMock()
|
440
|
+
mock_zot_class.return_value = mock_zot
|
441
|
+
yield mock_zot
|
442
|
+
|
443
|
+
@patch(
|
444
|
+
"aiagents4pharma.talk2scholars.tools.zotero.utils.zotero_path.get_item_collections"
|
445
|
+
)
|
446
|
+
def test_zotero_read_item_collections_error(
|
447
|
+
self, mock_get_collections, mock_hydra, mock_zotero
|
448
|
+
):
|
449
|
+
"""Test that zotero_read handles errors in get_item_collections."""
|
450
|
+
|
451
|
+
mock_get_collections.side_effect = Exception("Test error")
|
452
|
+
|
453
|
+
mock_zotero.items.return_value = [
|
454
|
+
{
|
455
|
+
"data": {
|
456
|
+
"key": "paper1",
|
457
|
+
"title": "Test Paper",
|
458
|
+
"itemType": "journalArticle",
|
459
|
+
}
|
460
|
+
}
|
461
|
+
]
|
462
|
+
mock_hydra.tools.zotero_read.zotero.max_limit = 50
|
463
|
+
|
464
|
+
result = zotero_read.run(
|
465
|
+
{
|
466
|
+
"query": "test",
|
467
|
+
"only_articles": True,
|
468
|
+
"tool_call_id": "test_id",
|
469
|
+
"limit": 2,
|
470
|
+
}
|
471
|
+
)
|
472
|
+
|
473
|
+
assert result is not None
|
474
|
+
assert isinstance(result.update, dict)
|
475
|
+
assert "zotero_read" in result.update
|
@@ -7,7 +7,7 @@ import unittest
|
|
7
7
|
from unittest.mock import patch, MagicMock
|
8
8
|
from langgraph.types import Command
|
9
9
|
from aiagents4pharma.talk2scholars.tools.zotero.zotero_read import (
|
10
|
-
|
10
|
+
zotero_read,
|
11
11
|
)
|
12
12
|
|
13
13
|
|
@@ -87,7 +87,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
87
87
|
"tool_call_id": tool_call_id,
|
88
88
|
"limit": 2,
|
89
89
|
}
|
90
|
-
result =
|
90
|
+
result = zotero_read.run(tool_input)
|
91
91
|
|
92
92
|
# Verify the Command update structure and contents
|
93
93
|
self.assertIsInstance(result, Command)
|
@@ -144,7 +144,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
144
144
|
"tool_call_id": tool_call_id,
|
145
145
|
"limit": 2,
|
146
146
|
}
|
147
|
-
result =
|
147
|
+
result = zotero_read.run(tool_input)
|
148
148
|
|
149
149
|
update = result.update
|
150
150
|
filtered_papers = update["zotero_read"]
|
@@ -183,7 +183,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
183
183
|
"limit": 2,
|
184
184
|
}
|
185
185
|
with self.assertRaises(RuntimeError) as context:
|
186
|
-
|
186
|
+
zotero_read.run(tool_input)
|
187
187
|
self.assertIn("No items returned from Zotero", str(context.exception))
|
188
188
|
|
189
189
|
@patch(
|
@@ -244,7 +244,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
244
244
|
"limit": 2,
|
245
245
|
}
|
246
246
|
# Instead of expecting a RuntimeError, we now expect both items to be returned.
|
247
|
-
result =
|
247
|
+
result = zotero_read.run(tool_input)
|
248
248
|
update = result.update
|
249
249
|
filtered_papers = update["zotero_read"]
|
250
250
|
self.assertIn("paper1", filtered_papers)
|
@@ -281,7 +281,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
281
281
|
"limit": 2,
|
282
282
|
}
|
283
283
|
with self.assertRaises(RuntimeError) as context:
|
284
|
-
|
284
|
+
zotero_read.run(tool_input)
|
285
285
|
self.assertIn("Failed to fetch items from Zotero", str(context.exception))
|
286
286
|
|
287
287
|
@patch(
|
@@ -336,7 +336,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
336
336
|
"tool_call_id": tool_call_id,
|
337
337
|
"limit": 2,
|
338
338
|
}
|
339
|
-
result =
|
339
|
+
result = zotero_read.run(tool_input)
|
340
340
|
|
341
341
|
update = result.update
|
342
342
|
filtered_papers = update["zotero_read"]
|
@@ -378,7 +378,7 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
378
378
|
"limit": 2,
|
379
379
|
}
|
380
380
|
with self.assertRaises(RuntimeError) as context:
|
381
|
-
|
381
|
+
zotero_read.run(tool_input)
|
382
382
|
self.assertIn("No matching papers returned from Zotero", str(context.exception))
|
383
383
|
|
384
384
|
@patch(
|
@@ -415,5 +415,5 @@ class TestZoteroSearchTool(unittest.TestCase):
|
|
415
415
|
"limit": 2,
|
416
416
|
}
|
417
417
|
with self.assertRaises(RuntimeError) as context:
|
418
|
-
|
418
|
+
zotero_read.run(tool_input)
|
419
419
|
self.assertIn("No matching papers returned from Zotero", str(context.exception))
|