Skip to content

Commit 57984aa

Browse files
author
phernandez
committed
chore: fix tests
1 parent f5a7541 commit 57984aa

File tree

3 files changed

+308
-12
lines changed

3 files changed

+308
-12
lines changed

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: install test lint clean format type-check installer-mac installer-win
1+
.PHONY: install test lint clean format type-check installer-mac installer-win check
22

33
install:
44
pip install -e ".[dev]"
@@ -40,4 +40,6 @@ installer-win:
4040

4141

4242
update-deps:
43-
uv lock f--upgrade
43+
uv lock f--upgrade
44+
45+
check: lint format type-check test

src/basic_memory/cli/commands/tools.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,23 @@ def write_note(
2929
tags: Annotated[
3030
Optional[List[str]], typer.Option(help="A list of tags to apply to the note")
3131
] = None,
32-
): # pragma: no cover
32+
):
3333
try:
3434
note = asyncio.run(mcp_write_note(title, content, folder, tags))
3535
rprint(note)
36-
except Exception as e:
36+
except Exception as e: # pragma: no cover
3737
if not isinstance(e, typer.Exit):
3838
typer.echo(f"Error during write_note: {e}", err=True)
3939
raise typer.Exit(1)
4040
raise
4141

4242

4343
@tool_app.command()
44-
def read_note(identifier: str, page: int = 1, page_size: int = 10): # pragma: no cover
44+
def read_note(identifier: str, page: int = 1, page_size: int = 10):
4545
try:
4646
note = asyncio.run(mcp_read_note(identifier, page, page_size))
4747
rprint(note)
48-
except Exception as e:
48+
except Exception as e: # pragma: no cover
4949
if not isinstance(e, typer.Exit):
5050
typer.echo(f"Error during read_note: {e}", err=True)
5151
raise typer.Exit(1)
@@ -60,7 +60,7 @@ def build_context(
6060
page: int = 1,
6161
page_size: int = 10,
6262
max_related: int = 10,
63-
): # pragma: no cover
63+
):
6464
try:
6565
context = asyncio.run(
6666
mcp_build_context(
@@ -88,16 +88,16 @@ def recent_activity(
8888
page: int = 1,
8989
page_size: int = 10,
9090
max_related: int = 10,
91-
): # pragma: no cover
92-
93-
if type not in ["entity", "observation", "relation"]:
91+
):
92+
assert type is not None, "type is required"
93+
if any(t not in ["entity", "observation", "relation"] for t in type): # pragma: no cover
9494
print("type must be one of ['entity', 'observation', 'relation']")
9595
raise typer.Abort()
9696

9797
try:
9898
context = asyncio.run(
9999
mcp_recent_activity(
100-
type=type,
100+
type=type, # pyright: ignore [reportArgumentType]
101101
depth=depth,
102102
timeframe=timeframe,
103103
page=page,
@@ -125,7 +125,7 @@ def search(
125125
page: int = 1,
126126
page_size: int = 10,
127127
):
128-
if permalink and title:
128+
if permalink and title: # pragma: no cover
129129
print("Cannot search both permalink and title")
130130
raise typer.Abort()
131131

tests/cli/test_cli_tools.py

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
"""Tests for the Basic Memory CLI tools."""
2+
3+
from datetime import datetime, timezone
4+
import pytest
5+
from typer.testing import CliRunner
6+
from unittest.mock import patch, AsyncMock
7+
8+
from basic_memory.cli.commands.tools import tool_app
9+
from basic_memory.schemas.response import EntityResponse
10+
from basic_memory.schemas.search import SearchResponse
11+
from basic_memory.schemas.memory import GraphContext
12+
13+
runner = CliRunner()
14+
15+
16+
@pytest.fixture
17+
def mock_write_note():
18+
with patch("basic_memory.cli.commands.tools.mcp_write_note", new_callable=AsyncMock) as mock:
19+
mock.return_value = "Created test/note.md (abc123)\npermalink: test/note"
20+
yield mock
21+
22+
23+
@pytest.fixture
24+
def mock_read_note():
25+
with patch("basic_memory.cli.commands.tools.mcp_read_note", new_callable=AsyncMock) as mock:
26+
mock.return_value = "--- memory://test/note 2025-01 abc123\nTest content"
27+
yield mock
28+
29+
30+
@pytest.fixture
31+
def mock_search():
32+
with patch("basic_memory.cli.commands.tools.mcp_search", new_callable=AsyncMock) as mock:
33+
mock.return_value = SearchResponse(results=[], current_page=1, page_size=10)
34+
yield mock
35+
36+
37+
@pytest.fixture
38+
def mock_build_context():
39+
with patch("basic_memory.cli.commands.tools.mcp_build_context", new_callable=AsyncMock) as mock:
40+
now = datetime.now(timezone.utc)
41+
mock.return_value = GraphContext(
42+
primary_results=[],
43+
related_results=[],
44+
metadata={
45+
"uri": "test/*",
46+
"depth": 1,
47+
"timeframe": "7d",
48+
"generated_at": now,
49+
"total_results": 0,
50+
"total_relations": 0,
51+
},
52+
)
53+
yield mock
54+
55+
56+
@pytest.fixture
57+
def mock_recent_activity():
58+
with patch(
59+
"basic_memory.cli.commands.tools.mcp_recent_activity", new_callable=AsyncMock
60+
) as mock:
61+
now = datetime.now(timezone.utc)
62+
mock.return_value = GraphContext(
63+
primary_results=[],
64+
related_results=[],
65+
metadata={
66+
"uri": None,
67+
"types": ["entity", "observation"],
68+
"depth": 1,
69+
"timeframe": "7d",
70+
"generated_at": now,
71+
"total_results": 0,
72+
"total_relations": 0,
73+
},
74+
)
75+
yield mock
76+
77+
78+
@pytest.fixture
79+
def mock_get_entity():
80+
with patch("basic_memory.cli.commands.tools.mcp_get_entity", new_callable=AsyncMock) as mock:
81+
now = datetime.now(timezone.utc)
82+
mock.return_value = EntityResponse(
83+
permalink="test/entity",
84+
title="Test Entity",
85+
file_path="test/entity.md",
86+
entity_type="note",
87+
content_type="text/markdown",
88+
observations=[],
89+
relations=[],
90+
created_at=now,
91+
updated_at=now,
92+
)
93+
yield mock
94+
95+
96+
def test_write_note(mock_write_note):
97+
"""Test write_note command with basic arguments."""
98+
result = runner.invoke(
99+
tool_app,
100+
[
101+
"write-note",
102+
"--title",
103+
"Test Note",
104+
"--content",
105+
"Test content",
106+
"--folder",
107+
"test",
108+
],
109+
)
110+
assert result.exit_code == 0
111+
mock_write_note.assert_awaited_once_with("Test Note", "Test content", "test", None)
112+
113+
114+
def test_write_note_with_tags(mock_write_note):
115+
"""Test write_note command with tags."""
116+
result = runner.invoke(
117+
tool_app,
118+
[
119+
"write-note",
120+
"--title",
121+
"Test Note",
122+
"--content",
123+
"Test content",
124+
"--folder",
125+
"test",
126+
"--tags",
127+
"tag1",
128+
"--tags",
129+
"tag2",
130+
],
131+
)
132+
assert result.exit_code == 0
133+
mock_write_note.assert_awaited_once_with("Test Note", "Test content", "test", ["tag1", "tag2"])
134+
135+
136+
def test_read_note(mock_read_note):
137+
"""Test read_note command."""
138+
result = runner.invoke(
139+
tool_app,
140+
["read-note", "test/note"],
141+
)
142+
assert result.exit_code == 0
143+
mock_read_note.assert_awaited_once_with("test/note", 1, 10)
144+
145+
146+
def test_read_note_with_pagination(mock_read_note):
147+
"""Test read_note command with pagination."""
148+
result = runner.invoke(
149+
tool_app,
150+
["read-note", "test/note", "--page", "2", "--page-size", "5"],
151+
)
152+
assert result.exit_code == 0
153+
mock_read_note.assert_awaited_once_with("test/note", 2, 5)
154+
155+
156+
def test_search_basic(mock_search):
157+
"""Test basic search command."""
158+
result = runner.invoke(
159+
tool_app,
160+
["search", "test query"],
161+
)
162+
assert result.exit_code == 0
163+
mock_search.assert_awaited_once()
164+
args = mock_search.await_args[1]
165+
assert args["query"].text == "test query"
166+
167+
168+
def test_search_permalink(mock_search):
169+
"""Test search with permalink flag."""
170+
result = runner.invoke(
171+
tool_app,
172+
["search", "test/*", "--permalink"],
173+
)
174+
assert result.exit_code == 0
175+
mock_search.assert_awaited_once()
176+
args = mock_search.await_args[1]
177+
assert args["query"].permalink_match == "test/*"
178+
179+
180+
def test_search_title(mock_search):
181+
"""Test search with title flag."""
182+
result = runner.invoke(
183+
tool_app,
184+
["search", "test", "--title"],
185+
)
186+
assert result.exit_code == 0
187+
mock_search.assert_awaited_once()
188+
args = mock_search.await_args[1]
189+
assert args["query"].title == "test"
190+
191+
192+
def test_search_with_pagination(mock_search):
193+
"""Test search with pagination."""
194+
result = runner.invoke(
195+
tool_app,
196+
["search", "test", "--page", "2", "--page-size", "5"],
197+
)
198+
assert result.exit_code == 0
199+
mock_search.assert_awaited_once()
200+
args = mock_search.await_args[1]
201+
assert args["page"] == 2
202+
assert args["page_size"] == 5
203+
204+
205+
def test_build_context(mock_build_context):
206+
"""Test build_context command."""
207+
result = runner.invoke(
208+
tool_app,
209+
["build-context", "memory://test/*"],
210+
)
211+
assert result.exit_code == 0
212+
mock_build_context.assert_awaited_once_with(
213+
url="memory://test/*", depth=1, timeframe="7d", page=1, page_size=10, max_related=10
214+
)
215+
216+
217+
def test_build_context_with_options(mock_build_context):
218+
"""Test build_context command with all options."""
219+
result = runner.invoke(
220+
tool_app,
221+
[
222+
"build-context",
223+
"memory://test/*",
224+
"--depth",
225+
"2",
226+
"--timeframe",
227+
"1d",
228+
"--page",
229+
"2",
230+
"--page-size",
231+
"5",
232+
"--max-related",
233+
"20",
234+
],
235+
)
236+
assert result.exit_code == 0
237+
mock_build_context.assert_awaited_once_with(
238+
url="memory://test/*", depth=2, timeframe="1d", page=2, page_size=5, max_related=20
239+
)
240+
241+
242+
def test_get_entity(mock_get_entity):
243+
"""Test get_entity command."""
244+
result = runner.invoke(
245+
tool_app,
246+
["get-entity", "test/entity"],
247+
)
248+
assert result.exit_code == 0
249+
mock_get_entity.assert_awaited_once_with(identifier="test/entity")
250+
251+
252+
def test_recent_activity(mock_recent_activity):
253+
"""Test recent_activity command with defaults."""
254+
result = runner.invoke(
255+
tool_app,
256+
["recent-activity"],
257+
)
258+
assert result.exit_code == 0
259+
mock_recent_activity.assert_awaited_once_with(
260+
type=["entity", "observation", "relation"],
261+
depth=1,
262+
timeframe="7d",
263+
page=1,
264+
page_size=10,
265+
max_related=10,
266+
)
267+
268+
269+
def test_recent_activity_with_options(mock_recent_activity):
270+
"""Test recent_activity command with options."""
271+
result = runner.invoke(
272+
tool_app,
273+
[
274+
"recent-activity",
275+
"--type",
276+
"entity",
277+
"--type",
278+
"observation",
279+
"--depth",
280+
"2",
281+
"--timeframe",
282+
"1d",
283+
"--page",
284+
"2",
285+
"--page-size",
286+
"5",
287+
"--max-related",
288+
"20",
289+
],
290+
)
291+
assert result.exit_code == 0
292+
mock_recent_activity.assert_awaited_once_with(
293+
type=["entity", "observation"], depth=2, timeframe="1d", page=2, page_size=5, max_related=20
294+
)

0 commit comments

Comments
 (0)