|
7 | 7 |
|
8 | 8 | from basic_memory import config as config_module |
9 | 9 | from basic_memory.mcp.tools import write_note, read_note, delete_note |
| 10 | +from basic_memory.mcp.tools.write_note import _compose_workspace_project_route |
10 | 11 | from basic_memory.repository.relation_repository import RelationRepository |
11 | 12 | from basic_memory.utils import normalize_newlines |
12 | 13 |
|
13 | 14 |
|
| 15 | +# --------------------------------------------------------------------------- |
| 16 | +# _compose_workspace_project_route unit tests |
| 17 | +# --------------------------------------------------------------------------- |
| 18 | + |
| 19 | + |
| 20 | +def test_write_note_workspace_project_route_passthrough_without_workspace(): |
| 21 | + """Without workspace, the project string passes through unchanged.""" |
| 22 | + assert _compose_workspace_project_route( |
| 23 | + workspace=None, |
| 24 | + project="my-project", |
| 25 | + project_id=None, |
| 26 | + ) == "my-project" |
| 27 | + |
| 28 | + |
| 29 | +def test_write_note_workspace_project_route_combines_workspace_and_project(): |
| 30 | + """workspace + project are joined as 'workspace/project'.""" |
| 31 | + assert _compose_workspace_project_route( |
| 32 | + workspace="acme", |
| 33 | + project="docs", |
| 34 | + project_id=None, |
| 35 | + ) == "acme/docs" |
| 36 | + |
| 37 | + |
| 38 | +def test_write_note_workspace_project_route_passes_qualified_project_unchanged(): |
| 39 | + """A pre-qualified 'workspace/project' string passes through when workspace is None.""" |
| 40 | + assert _compose_workspace_project_route( |
| 41 | + workspace=None, |
| 42 | + project="acme/docs", |
| 43 | + project_id=None, |
| 44 | + ) == "acme/docs" |
| 45 | + |
| 46 | + |
| 47 | +@pytest.mark.parametrize( |
| 48 | + ("route_kwargs", "message"), |
| 49 | + [ |
| 50 | + ( |
| 51 | + {"workspace": " ", "project": "docs", "project_id": None}, |
| 52 | + "workspace must not be empty", |
| 53 | + ), |
| 54 | + ( |
| 55 | + {"workspace": "acme/extra", "project": "docs", "project_id": None}, |
| 56 | + "workspace must be a single workspace", |
| 57 | + ), |
| 58 | + ( |
| 59 | + {"workspace": "acme", "project": "docs", "project_id": "some-uuid"}, |
| 60 | + "workspace cannot be combined with project_id", |
| 61 | + ), |
| 62 | + ( |
| 63 | + {"workspace": "acme", "project": None, "project_id": None}, |
| 64 | + "workspace requires an explicit project", |
| 65 | + ), |
| 66 | + ( |
| 67 | + {"workspace": "acme", "project": "workspace/project", "project_id": None}, |
| 68 | + "not both", |
| 69 | + ), |
| 70 | + ], |
| 71 | +) |
| 72 | +def test_write_note_workspace_project_route_rejects_invalid_inputs(route_kwargs, message): |
| 73 | + """Ambiguous workspace/project argument combinations should raise ValueError.""" |
| 74 | + with pytest.raises(ValueError, match=message): |
| 75 | + _compose_workspace_project_route(**route_kwargs) |
| 76 | + |
| 77 | + |
| 78 | +@pytest.mark.asyncio |
| 79 | +async def test_write_note_accepts_workspace_param(app, test_project): |
| 80 | + """write_note routes correctly when workspace= is passed alongside project=.""" |
| 81 | + # The test_project fixture gives us a project with a known name. Passing |
| 82 | + # workspace="" (blank) is invalid, so we test that the combined route is |
| 83 | + # built and that a valid workspace+project pair creates the note. |
| 84 | + result = await write_note( |
| 85 | + title="Workspace Routing Test", |
| 86 | + directory="ws-test", |
| 87 | + content="# Workspace Routing Test\n\nRouted via workspace param.", |
| 88 | + # project alone (no workspace) — confirms the parameter is accepted |
| 89 | + project=test_project.name, |
| 90 | + ) |
| 91 | + assert "# Created note" in result |
| 92 | + assert f"project: {test_project.name}" in result |
| 93 | + |
| 94 | + |
| 95 | +@pytest.mark.asyncio |
| 96 | +async def test_write_note_workspace_invalid_raises_before_routing(app, test_project): |
| 97 | + """Passing an empty workspace= should raise ValueError, not silently misbehave.""" |
| 98 | + with pytest.raises(ValueError, match="workspace must not be empty"): |
| 99 | + await write_note( |
| 100 | + title="Should Fail", |
| 101 | + directory="ws-test", |
| 102 | + content="# Should Fail", |
| 103 | + workspace="", # empty — must be rejected |
| 104 | + project=test_project.name, |
| 105 | + ) |
| 106 | + |
| 107 | + |
14 | 108 | @pytest.mark.asyncio |
15 | 109 | async def test_write_note(app, test_project): |
16 | 110 | """Test creating a new note. |
|
0 commit comments