@@ -220,63 +220,105 @@ async def test_list_memory_projects_cloud_failure_graceful(app, test_project):
220220
221221@pytest .mark .asyncio
222222async def test_list_memory_projects_factory_mode (app , test_project ):
223- """In factory mode (cloud app), projects are reported as cloud-sourced with workspace metadata."""
224- factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
225- factory_list = _make_list ([factory_project ], default = "cloud-proj" )
226-
227- ws = _make_workspace ("tenant-abc" , "Personal" , "personal" )
223+ """Factory mode lists projects from every accessible workspace."""
224+ personal_project = _make_project (
225+ "personal-main" ,
226+ "/personal-main" ,
227+ is_default = True ,
228+ external_id = "personal-project-uuid" ,
229+ )
230+ team_project = _make_project (
231+ "team-specs" ,
232+ "/team-specs" ,
233+ id = 2 ,
234+ external_id = "team-project-uuid" ,
235+ )
236+ personal_ws = _make_workspace (
237+ "personal-tenant" ,
238+ "Personal" ,
239+ slug = "personal" ,
240+ is_default = True ,
241+ )
242+ team_ws = _make_workspace (
243+ "team-tenant" ,
244+ "Team Paul" ,
245+ "organization" ,
246+ slug = "team-paul" ,
247+ )
248+ workspace_index = _make_workspace_index (
249+ [
250+ (personal_ws , [personal_project ]),
251+ (team_ws , [team_project ]),
252+ ]
253+ )
228254
229255 with (
230256 patch (
231257 "basic_memory.mcp.tools.project_management.is_factory_mode" ,
232258 return_value = True ,
233259 ),
234260 patch (
235- "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
236- new_callable = AsyncMock ,
237- return_value = factory_list ,
238- ),
239- patch (
240- "basic_memory.mcp.project_context.get_available_workspaces" ,
261+ "basic_memory.mcp.tools.project_management.ensure_workspace_project_index" ,
241262 new_callable = AsyncMock ,
242- return_value = [ ws ] ,
243- ),
263+ return_value = workspace_index ,
264+ ) as mock_index ,
244265 ):
245266 result = await list_memory_projects ()
246267
247- assert "- cloud-proj (cloud)" in result
268+ mock_index .assert_awaited_once ()
269+ assert "Workspace: Personal (personal default)" in result
270+ assert "Workspace: Team Paul (team-paul)" in result
271+ assert "- personal-main (cloud) [personal-project-uuid]" in result
272+ assert "- team-specs (cloud) [team-project-uuid]" in result
248273
249274
250275@pytest .mark .asyncio
251276async def test_list_memory_projects_factory_mode_json_includes_workspace (app , test_project ):
252- """In factory mode, JSON output includes workspace metadata for cloud projects."""
253- factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
254- factory_list = _make_list ([factory_project ], default = "cloud-proj" )
255-
256- ws = _make_workspace ("tenant-abc" , "My Org" , "organization" )
277+ """In factory mode, JSON output includes workspace metadata for all cloud projects."""
278+ default_project = _make_project (
279+ "personal-main" ,
280+ "/personal-main" ,
281+ is_default = True ,
282+ external_id = "personal-project-uuid" ,
283+ )
284+ org_project = _make_project (
285+ "cloud-proj" ,
286+ "/cloud-proj" ,
287+ id = 2 ,
288+ external_id = "org-project-uuid" ,
289+ )
290+ personal_ws = _make_workspace (
291+ "personal-tenant" ,
292+ "Personal" ,
293+ slug = "personal" ,
294+ is_default = True ,
295+ )
296+ org_ws = _make_workspace ("tenant-abc" , "My Org" , "organization" )
297+ workspace_index = _make_workspace_index (
298+ [
299+ (personal_ws , [default_project ]),
300+ (org_ws , [org_project ]),
301+ ]
302+ )
257303
258304 with (
259305 patch (
260306 "basic_memory.mcp.tools.project_management.is_factory_mode" ,
261307 return_value = True ,
262308 ),
263309 patch (
264- "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
265- new_callable = AsyncMock ,
266- return_value = factory_list ,
267- ),
268- patch (
269- "basic_memory.mcp.project_context.get_available_workspaces" ,
310+ "basic_memory.mcp.tools.project_management.ensure_workspace_project_index" ,
270311 new_callable = AsyncMock ,
271- return_value = [ ws ] ,
312+ return_value = workspace_index ,
272313 ),
273314 ):
274315 result = await list_memory_projects (output_format = "json" )
275316
276317 assert isinstance (result , dict )
318+ assert result ["default_project" ] == "personal-main"
277319 projects = result ["projects" ]
278- assert len (projects ) == 1
279- proj = projects [ 0 ]
320+ assert len (projects ) == 2
321+ proj = { project [ "name" ]: project for project in projects }[ "cloud-proj" ]
280322 assert proj ["source" ] == "cloud"
281323 assert proj ["cloud_path" ] == "/cloud-proj"
282324 assert proj ["local_path" ] is None
@@ -290,30 +332,20 @@ async def test_list_memory_projects_factory_mode_json_includes_workspace(app, te
290332
291333@pytest .mark .asyncio
292334async def test_list_memory_projects_factory_mode_workspace_lookup_failure (app , test_project ):
293- """In factory mode, workspace lookup failure still returns projects as cloud-sourced."""
294- factory_project = _make_project ("cloud-proj" , "/cloud-proj" , is_default = True )
295- factory_list = _make_list ([factory_project ], default = "cloud-proj" )
296-
335+ """In factory mode, workspace discovery failures are surfaced to the caller."""
297336 with (
298337 patch (
299338 "basic_memory.mcp.tools.project_management.is_factory_mode" ,
300339 return_value = True ,
301340 ),
302341 patch (
303- "basic_memory.mcp.clients.project.ProjectClient.list_projects" ,
304- new_callable = AsyncMock ,
305- return_value = factory_list ,
306- ),
307- patch (
308- "basic_memory.mcp.project_context.get_available_workspaces" ,
342+ "basic_memory.mcp.tools.project_management.ensure_workspace_project_index" ,
309343 new_callable = AsyncMock ,
310344 side_effect = RuntimeError ("no user context" ),
311345 ),
312346 ):
313- result = await list_memory_projects ()
314-
315- # Still reported as cloud even without workspace metadata
316- assert "- cloud-proj (cloud)" in result
347+ with pytest .raises (RuntimeError , match = "no user context" ):
348+ await list_memory_projects ()
317349
318350
319351@pytest .mark .asyncio
0 commit comments