|
5 | 5 | tag handling, error conditions, and edge cases from bug reports. |
6 | 6 | """ |
7 | 7 |
|
| 8 | +import json |
| 9 | +from pathlib import Path |
8 | 10 | from textwrap import dedent |
| 11 | +from typing import Any |
9 | 12 |
|
10 | 13 | import pytest |
11 | 14 | from fastmcp import Client |
12 | 15 |
|
13 | 16 | from basic_memory.config import ConfigManager |
14 | 17 | from basic_memory.schemas.project_info import ProjectItem |
15 | | -from pathlib import Path |
| 18 | + |
| 19 | + |
| 20 | +def _json_content(tool_result) -> dict[str, Any]: |
| 21 | + """Parse a FastMCP tool result content block into a JSON object.""" |
| 22 | + assert len(tool_result.content) == 1 |
| 23 | + assert tool_result.content[0].type == "text" |
| 24 | + payload = json.loads(tool_result.content[0].text) # pyright: ignore [reportAttributeAccessIssue] |
| 25 | + assert isinstance(payload, dict) |
| 26 | + return payload |
16 | 27 |
|
17 | 28 |
|
18 | 29 | @pytest.mark.asyncio |
@@ -113,6 +124,83 @@ async def test_write_note_update_existing(mcp_server, app, test_project): |
113 | 124 | assert f"[Session: Using project '{test_project.name}']" in response_text |
114 | 125 |
|
115 | 126 |
|
| 127 | +@pytest.mark.asyncio |
| 128 | +async def test_write_note_overwrite_resolves_conflict_by_file_path_when_permalink_changes( |
| 129 | + mcp_server, app, test_project, monkeypatch |
| 130 | +): |
| 131 | + """Overwrite resolves the conflict by strict file path through the MCP client stack.""" |
| 132 | + from basic_memory.mcp.clients import knowledge as knowledge_mod |
| 133 | + |
| 134 | + original_resolve = knowledge_mod.KnowledgeClient.resolve_entity |
| 135 | + captured_resolve: dict[str, Any] = {} |
| 136 | + |
| 137 | + async def spy_resolve(self, identifier: str, *, strict: bool = False) -> str: |
| 138 | + captured_resolve["identifier"] = identifier |
| 139 | + captured_resolve["strict"] = strict |
| 140 | + return await original_resolve(self, identifier, strict=strict) |
| 141 | + |
| 142 | + monkeypatch.setattr(knowledge_mod.KnowledgeClient, "resolve_entity", spy_resolve) |
| 143 | + |
| 144 | + async with Client(mcp_server) as client: |
| 145 | + created = await client.call_tool( |
| 146 | + "write_note", |
| 147 | + { |
| 148 | + "project": test_project.name, |
| 149 | + "title": "Overwrite Permalink Change", |
| 150 | + "directory": "overwrite-conflicts", |
| 151 | + "content": "# Overwrite Permalink Change\n\nOriginal body.", |
| 152 | + "output_format": "json", |
| 153 | + }, |
| 154 | + ) |
| 155 | + created_payload = _json_content(created) |
| 156 | + assert created_payload["permalink"] == ( |
| 157 | + f"{test_project.name}/overwrite-conflicts/overwrite-permalink-change" |
| 158 | + ) |
| 159 | + |
| 160 | + replacement = dedent(""" |
| 161 | + --- |
| 162 | + permalink: overwrite-conflicts/custom-overwrite-permalink |
| 163 | + --- |
| 164 | +
|
| 165 | + # Overwrite Permalink Change |
| 166 | +
|
| 167 | + Replacement body. |
| 168 | + """).strip() |
| 169 | + |
| 170 | + updated = await client.call_tool( |
| 171 | + "write_note", |
| 172 | + { |
| 173 | + "project": test_project.name, |
| 174 | + "title": "Overwrite Permalink Change", |
| 175 | + "directory": "overwrite-conflicts", |
| 176 | + "content": replacement, |
| 177 | + "overwrite": True, |
| 178 | + "output_format": "json", |
| 179 | + }, |
| 180 | + ) |
| 181 | + updated_payload = _json_content(updated) |
| 182 | + assert updated_payload["action"] == "updated" |
| 183 | + assert updated_payload["permalink"] == "overwrite-conflicts/custom-overwrite-permalink" |
| 184 | + assert updated_payload["file_path"] == "overwrite-conflicts/Overwrite Permalink Change.md" |
| 185 | + assert captured_resolve == { |
| 186 | + "identifier": "overwrite-conflicts/Overwrite Permalink Change.md", |
| 187 | + "strict": True, |
| 188 | + } |
| 189 | + |
| 190 | + read_updated = await client.call_tool( |
| 191 | + "read_note", |
| 192 | + { |
| 193 | + "project": test_project.name, |
| 194 | + "identifier": "overwrite-conflicts/custom-overwrite-permalink", |
| 195 | + "output_format": "json", |
| 196 | + }, |
| 197 | + ) |
| 198 | + read_payload = _json_content(read_updated) |
| 199 | + assert read_payload["permalink"] == "overwrite-conflicts/custom-overwrite-permalink" |
| 200 | + assert "Replacement body." in read_payload["content"] |
| 201 | + assert "Original body." not in read_payload["content"] |
| 202 | + |
| 203 | + |
116 | 204 | @pytest.mark.asyncio |
117 | 205 | async def test_write_note_tag_array(mcp_server, app, test_project): |
118 | 206 | """Test creating a note with tag array (Issue #38 regression test).""" |
|
0 commit comments