"""Tests for mempalace.room_detector_local.""" from unittest.mock import MagicMock, patch from mempalace.room_detector_local import ( FOLDER_ROOM_MAP, detect_rooms_from_files, detect_rooms_from_folders, detect_rooms_local, get_user_approval, print_proposed_structure, save_config, ) # ── FOLDER_ROOM_MAP ──────────────────────────────────────────────────── def test_folder_room_map_has_expected_mappings(): assert FOLDER_ROOM_MAP["frontend"] == "frontend" assert FOLDER_ROOM_MAP["backend"] == "backend" assert FOLDER_ROOM_MAP["docs"] == "documentation" assert FOLDER_ROOM_MAP["tests"] == "testing" assert FOLDER_ROOM_MAP["config"] == "configuration" def test_folder_room_map_alternative_names(): assert FOLDER_ROOM_MAP["front-end"] == "frontend" assert FOLDER_ROOM_MAP["back-end"] == "backend" assert FOLDER_ROOM_MAP["server"] == "backend" assert FOLDER_ROOM_MAP["client"] == "frontend" assert FOLDER_ROOM_MAP["api"] == "backend" # ── detect_rooms_from_folders ─────────────────────────────────────────── def test_detect_rooms_from_folders_standard_layout(tmp_path): (tmp_path / "frontend").mkdir() (tmp_path / "backend").mkdir() (tmp_path / "docs").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) room_names = {r["name"] for r in rooms} assert "frontend" in room_names assert "backend" in room_names assert "documentation" in room_names def test_detect_rooms_from_folders_always_has_general(tmp_path): rooms = detect_rooms_from_folders(str(tmp_path)) room_names = {r["name"] for r in rooms} assert "general" in room_names def test_detect_rooms_from_folders_empty_dir(tmp_path): rooms = detect_rooms_from_folders(str(tmp_path)) # Should at least have "general" assert len(rooms) >= 1 assert any(r["name"] == "general" for r in rooms) def test_detect_rooms_from_folders_skips_git(tmp_path): (tmp_path / ".git").mkdir() (tmp_path / "node_modules").mkdir() (tmp_path / "frontend").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) room_names = {r["name"] for r in rooms} assert ".git" not in room_names assert "node_modules" not in room_names def test_detect_rooms_from_folders_nested_dirs(tmp_path): src = tmp_path / "src" src.mkdir() (src / "components").mkdir() (src / "routes").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) room_names = {r["name"] for r in rooms} # Nested dirs should be detected at one level deep assert "frontend" in room_names or "backend" in room_names def test_detect_rooms_from_folders_room_has_description(tmp_path): (tmp_path / "docs").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) doc_room = next((r for r in rooms if r["name"] == "documentation"), None) assert doc_room is not None assert "description" in doc_room assert "docs" in doc_room["description"] def test_detect_rooms_from_folders_room_has_keywords(tmp_path): (tmp_path / "frontend").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) fe_room = next((r for r in rooms if r["name"] == "frontend"), None) assert fe_room is not None assert "keywords" in fe_room assert len(fe_room["keywords"]) > 0 def test_detect_rooms_from_folders_custom_named_dirs(tmp_path): (tmp_path / "mylib").mkdir() rooms = detect_rooms_from_folders(str(tmp_path)) room_names = {r["name"] for r in rooms} # Custom dir names that don't match FOLDER_ROOM_MAP get added as-is assert "mylib" in room_names or "general" in room_names # ── detect_rooms_from_files ───────────────────────────────────────────── def test_detect_rooms_from_files_with_matching_filenames(tmp_path): # Create files whose names contain room keywords for name in ["test_auth.py", "test_login.py", "test_api.py"]: (tmp_path / name).write_text("content") rooms = detect_rooms_from_files(str(tmp_path)) room_names = {r["name"] for r in rooms} assert "testing" in room_names or "general" in room_names def test_detect_rooms_from_files_empty_dir(tmp_path): rooms = detect_rooms_from_files(str(tmp_path)) assert len(rooms) >= 1 assert any(r["name"] == "general" for r in rooms) def test_detect_rooms_from_files_caps_at_six(tmp_path): # Create many files with different keywords to hit the cap for keyword in ["test", "doc", "api", "config", "frontend", "backend", "design", "meeting"]: for i in range(3): (tmp_path / f"{keyword}_file_{i}.txt").write_text("content") rooms = detect_rooms_from_files(str(tmp_path)) assert len(rooms) <= 6 # ── save_config ───────────────────────────────────────────────────────── def test_save_config_creates_yaml(tmp_path): rooms = [ {"name": "frontend", "description": "UI files", "keywords": ["frontend"]}, {"name": "backend", "description": "Server files", "keywords": ["backend"]}, ] save_config(str(tmp_path), "myproject", rooms) config_file = tmp_path / "mempalace.yaml" assert config_file.exists() content = config_file.read_text() assert "myproject" in content assert "frontend" in content assert "backend" in content def test_save_config_valid_yaml(tmp_path): import yaml rooms = [{"name": "general", "description": "All files", "keywords": []}] save_config(str(tmp_path), "test_proj", rooms) config_file = tmp_path / "mempalace.yaml" data = yaml.safe_load(config_file.read_text()) assert data["wing"] == "test_proj" assert len(data["rooms"]) == 1 assert data["rooms"][0]["name"] == "general" # ── print_proposed_structure ────────────────────────────────────────── def test_print_proposed_structure(capsys): rooms = [ {"name": "frontend", "description": "UI files"}, {"name": "general", "description": "Everything else"}, ] print_proposed_structure("myapp", rooms, 42, "folder structure") out = capsys.readouterr().out assert "myapp" in out assert "frontend" in out assert "42 files" in out assert "folder structure" in out # ── get_user_approval ───────────────────────────────────────────────── def test_get_user_approval_accept_all(): rooms = [{"name": "frontend", "description": "UI"}] with patch("builtins.input", return_value=""): result = get_user_approval(rooms) assert result == rooms def test_get_user_approval_edit_remove(): rooms = [ {"name": "frontend", "description": "UI"}, {"name": "backend", "description": "Server"}, ] with patch("builtins.input", side_effect=["edit", "1", "n"]): result = get_user_approval(rooms) # Room 1 (frontend) removed assert len(result) == 1 assert result[0]["name"] == "backend" def test_get_user_approval_add_room(): rooms = [{"name": "general", "description": "All files"}] with patch( "builtins.input", side_effect=[ "add", "custom_room", "My custom room", "", ], ): result = get_user_approval(rooms) names = [r["name"] for r in result] assert "custom_room" in names # ── detect_rooms_local ──────────────────────────────────────────────── def test_detect_rooms_local_yes_mode(tmp_path): (tmp_path / "docs").mkdir() (tmp_path / "docs" / "readme.md").write_text("hello") mock_miner = MagicMock() mock_miner.scan_project.return_value = ["file1.py"] with patch.dict("sys.modules", {"mempalace.miner": mock_miner}): detect_rooms_local(str(tmp_path), yes=True) assert (tmp_path / "mempalace.yaml").exists() def test_detect_rooms_local_fallback_to_files(tmp_path): """When folder detection gives only 'general', falls back to file patterns.""" for i in range(3): (tmp_path / f"test_file_{i}.py").write_text("content") mock_miner = MagicMock() mock_miner.scan_project.return_value = ["f1", "f2"] with patch.dict("sys.modules", {"mempalace.miner": mock_miner}): detect_rooms_local(str(tmp_path), yes=True) assert (tmp_path / "mempalace.yaml").exists() def test_detect_rooms_local_missing_dir(): """Non-existent directory causes sys.exit.""" import pytest with pytest.raises(SystemExit): detect_rooms_local("/nonexistent/path/that/does/not/exist", yes=True) def test_detect_rooms_local_interactive(tmp_path): (tmp_path / "src").mkdir() (tmp_path / "src" / "main.py").write_text("code") mock_miner = MagicMock() mock_miner.scan_project.return_value = ["f1"] with ( patch.dict("sys.modules", {"mempalace.miner": mock_miner}), patch( "mempalace.room_detector_local.get_user_approval", return_value=[{"name": "general", "description": "All files", "keywords": []}], ), ): detect_rooms_local(str(tmp_path), yes=False) assert (tmp_path / "mempalace.yaml").exists()