@@ -214,10 +214,12 @@ async def test_list_memory_projects_cloud_failure_graceful(app, test_project):
214214
215215@pytest .mark .asyncio
216216async def test_list_memory_projects_factory_mode (app , test_project ):
217- """In factory mode (cloud app), only the factory client is used — no cloud merge ."""
217+ """In factory mode (cloud app), projects are reported as cloud-sourced with workspace metadata ."""
218218 factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
219219 factory_list = _make_list ([factory_project ], default = "cloud-proj" )
220220
221+ ws = _make_workspace ("tenant-abc" , "Personal" , "personal" )
222+
221223 with (
222224 patch (
223225 "basic_memory.mcp.tools.project_management.is_factory_mode" ,
@@ -228,12 +230,114 @@ async def test_list_memory_projects_factory_mode(app, test_project):
228230 new_callable = AsyncMock ,
229231 return_value = factory_list ,
230232 ),
233+ patch (
234+ "basic_memory.mcp.project_context.get_available_workspaces" ,
235+ new_callable = AsyncMock ,
236+ return_value = [ws ],
237+ ),
231238 ):
232239 result = await list_memory_projects ()
233240
234- assert "• cloud-proj (local)" in result
235- # has_cloud_credentials should not be called in factory mode
236- # (no cloud merge attempt)
241+ assert "• cloud-proj (cloud)" in result
242+
243+
244+ @pytest .mark .asyncio
245+ async def test_list_memory_projects_factory_mode_json_includes_workspace (app , test_project ):
246+ """In factory mode, JSON output includes workspace metadata for cloud projects."""
247+ factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
248+ factory_list = _make_list ([factory_project ], default = "cloud-proj" )
249+
250+ ws = _make_workspace ("tenant-abc" , "My Org" , "organization" )
251+
252+ with (
253+ patch (
254+ "basic_memory.mcp.tools.project_management.is_factory_mode" ,
255+ return_value = True ,
256+ ),
257+ patch (
258+ "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
259+ new_callable = AsyncMock ,
260+ return_value = factory_list ,
261+ ),
262+ patch (
263+ "basic_memory.mcp.project_context.get_available_workspaces" ,
264+ new_callable = AsyncMock ,
265+ return_value = [ws ],
266+ ),
267+ ):
268+ result = await list_memory_projects (output_format = "json" )
269+
270+ assert isinstance (result , dict )
271+ projects = result ["projects" ]
272+ assert len (projects ) == 1
273+ proj = projects [0 ]
274+ assert proj ["source" ] == "cloud"
275+ assert proj ["cloud_path" ] == "/cloud-proj"
276+ assert proj ["local_path" ] is None
277+ assert proj ["workspace_name" ] == "My Org"
278+ assert proj ["workspace_type" ] == "organization"
279+ assert proj ["workspace_tenant_id" ] == "tenant-abc"
280+
281+
282+ @pytest .mark .asyncio
283+ async def test_list_memory_projects_factory_mode_workspace_lookup_failure (app , test_project ):
284+ """In factory mode, workspace lookup failure still returns projects as cloud-sourced."""
285+ factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
286+ factory_list = _make_list ([factory_project ], default = "cloud-proj" )
287+
288+ with (
289+ patch (
290+ "basic_memory.mcp.tools.project_management.is_factory_mode" ,
291+ return_value = True ,
292+ ),
293+ patch (
294+ "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
295+ new_callable = AsyncMock ,
296+ return_value = factory_list ,
297+ ),
298+ patch (
299+ "basic_memory.mcp.project_context.get_available_workspaces" ,
300+ new_callable = AsyncMock ,
301+ side_effect = RuntimeError ("no user context" ),
302+ ),
303+ ):
304+ result = await list_memory_projects ()
305+
306+ # Still reported as cloud even without workspace metadata
307+ assert "• cloud-proj (cloud)" in result
308+
309+
310+ @pytest .mark .asyncio
311+ async def test_list_memory_projects_factory_mode_explicit_workspace (app , test_project ):
312+ """In factory mode, explicit workspace param selects the matching workspace."""
313+ factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
314+ factory_list = _make_list ([factory_project ], default = "cloud-proj" )
315+
316+ personal_ws = _make_workspace ("tenant-personal" , "Personal" , "personal" )
317+ org_ws = _make_workspace ("tenant-org" , "Acme Corp" , "organization" )
318+
319+ with (
320+ patch (
321+ "basic_memory.mcp.tools.project_management.is_factory_mode" ,
322+ return_value = True ,
323+ ),
324+ patch (
325+ "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
326+ new_callable = AsyncMock ,
327+ return_value = factory_list ,
328+ ),
329+ patch (
330+ "basic_memory.mcp.project_context.get_available_workspaces" ,
331+ new_callable = AsyncMock ,
332+ return_value = [personal_ws , org_ws ],
333+ ),
334+ ):
335+ result = await list_memory_projects (output_format = "json" , workspace = "tenant-org" )
336+
337+ proj = result ["projects" ][0 ]
338+ assert proj ["workspace_name" ] == "Acme Corp"
339+ assert proj ["workspace_type" ] == "organization"
340+ assert proj ["workspace_tenant_id" ] == "tenant-org"
237341
238342
239343@pytest .mark .asyncio
0 commit comments