Skip to content

Commit df5fc72

Browse files
committed
test(mcp): cover workspace metadata provider gaps
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 45b3dff commit df5fc72

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

tests/mcp/test_project_context.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,134 @@ async def fake_get_active_project(client, project_name, context=None, headers=No
17171717
assert permalink_context.workspace_slug == "team-paul"
17181718
assert permalink_context.workspace_type == "organization"
17191719

1720+
@pytest.mark.asyncio
1721+
async def test_cloud_project_workspace_id_uses_cached_available_workspaces_for_permalink_context(
1722+
self, config_manager, monkeypatch
1723+
):
1724+
"""Cached workspace metadata supplies slug/type before the provider is consulted."""
1725+
from contextlib import asynccontextmanager
1726+
1727+
import basic_memory.mcp.project_context as project_context
1728+
from basic_memory.mcp.project_context import get_project_client
1729+
from basic_memory.config import ProjectEntry, ProjectMode
1730+
from basic_memory.schemas.project_info import ProjectItem
1731+
from basic_memory.workspace_context import (
1732+
WorkspacePermalinkContext,
1733+
current_workspace_permalink_context,
1734+
)
1735+
1736+
config = config_manager.load_config()
1737+
config.projects["cloud-proj"] = ProjectEntry(
1738+
path=str(config_manager.config_dir.parent / "cloud-proj"),
1739+
mode=ProjectMode.CLOUD,
1740+
workspace_id="per-project-tenant-id",
1741+
)
1742+
config.cloud_api_key = "bmc_test123"
1743+
config_manager.save_config(config)
1744+
1745+
workspace = _workspace(
1746+
tenant_id="per-project-tenant-id",
1747+
workspace_type="organization",
1748+
slug="team-paul",
1749+
name="Team Paul",
1750+
role="editor",
1751+
)
1752+
context = _ContextState()
1753+
await context.set_state("available_workspaces", ["ignored", workspace.model_dump()])
1754+
1755+
async def fail_workspace_provider(): # pragma: no cover
1756+
raise AssertionError("Cached workspace metadata should be used before provider")
1757+
1758+
async def fail_ensure_workspace_project_index(context=None): # pragma: no cover
1759+
raise AssertionError("Configured workspace_id should not require project discovery")
1760+
1761+
monkeypatch.setattr(project_context, "_workspace_provider", fail_workspace_provider)
1762+
monkeypatch.setattr(
1763+
project_context,
1764+
"_ensure_workspace_project_index",
1765+
fail_ensure_workspace_project_index,
1766+
)
1767+
1768+
seen: dict[str, object] = {}
1769+
1770+
@asynccontextmanager
1771+
async def fake_get_client(project_name=None, workspace=None):
1772+
seen["project_name"] = project_name
1773+
seen["workspace"] = workspace
1774+
seen["permalink_context"] = current_workspace_permalink_context()
1775+
yield object()
1776+
1777+
async def fake_get_active_project(client, project_name, context=None, headers=None):
1778+
return ProjectItem(
1779+
id=1,
1780+
external_id="cloud-project-id",
1781+
name=project_name,
1782+
path="/cloud-proj",
1783+
is_default=False,
1784+
)
1785+
1786+
monkeypatch.setattr("basic_memory.mcp.async_client.get_client", fake_get_client)
1787+
monkeypatch.setattr(project_context, "get_active_project", fake_get_active_project)
1788+
1789+
async with get_project_client(
1790+
project="cloud-proj",
1791+
context=_ctx(context),
1792+
) as (_client, active_project):
1793+
assert active_project.external_id == "cloud-project-id"
1794+
1795+
assert seen["project_name"] == "cloud-proj"
1796+
assert seen["workspace"] == "per-project-tenant-id"
1797+
permalink_context = cast(WorkspacePermalinkContext | None, seen["permalink_context"])
1798+
assert permalink_context is not None
1799+
assert permalink_context.workspace_slug == "team-paul"
1800+
assert permalink_context.workspace_type == "organization"
1801+
1802+
@pytest.mark.asyncio
1803+
async def test_cloud_project_workspace_id_errors_when_provider_omits_workspace(
1804+
self, config_manager, monkeypatch
1805+
):
1806+
"""Provider metadata must include the configured tenant before slug/type forwarding."""
1807+
import basic_memory.mcp.project_context as project_context
1808+
from basic_memory.mcp.project_context import get_project_client
1809+
from basic_memory.config import ProjectEntry, ProjectMode
1810+
1811+
config = config_manager.load_config()
1812+
config.projects["cloud-proj"] = ProjectEntry(
1813+
path=str(config_manager.config_dir.parent / "cloud-proj"),
1814+
mode=ProjectMode.CLOUD,
1815+
workspace_id="per-project-tenant-id",
1816+
)
1817+
config.cloud_api_key = "bmc_test123"
1818+
config_manager.save_config(config)
1819+
1820+
async def fake_workspace_provider():
1821+
return [
1822+
_workspace(
1823+
tenant_id="other-tenant-id",
1824+
workspace_type="organization",
1825+
slug="other-team",
1826+
name="Other Team",
1827+
role="editor",
1828+
)
1829+
]
1830+
1831+
async def fail_ensure_workspace_project_index(context=None): # pragma: no cover
1832+
raise AssertionError("Configured workspace_id should not require project discovery")
1833+
1834+
monkeypatch.setattr(project_context, "_workspace_provider", fake_workspace_provider)
1835+
monkeypatch.setattr(
1836+
project_context,
1837+
"_ensure_workspace_project_index",
1838+
fail_ensure_workspace_project_index,
1839+
)
1840+
1841+
with pytest.raises(
1842+
ValueError,
1843+
match="Configured workspace_id 'per-project-tenant-id' was not returned",
1844+
):
1845+
async with get_project_client(project="cloud-proj"):
1846+
pass
1847+
17201848
@pytest.mark.asyncio
17211849
async def test_cloud_project_workspace_id_routes_without_discovery(
17221850
self, config_manager, monkeypatch

0 commit comments

Comments
 (0)