2121"""
2222
2323import json
24+ from collections .abc import Iterator
25+ from contextlib import contextmanager
2426from pathlib import Path
2527from unittest .mock import patch
2628
@@ -74,6 +76,33 @@ def assert_install_added_hooks(settings_before: dict, settings_after: dict) -> N
7476 )
7577
7678
79+ @contextmanager
80+ def mock_local_claude_settings (
81+ tmp_path : Path , content : str | dict = '{"local": "unchanged"}'
82+ ) -> Iterator [Path ]:
83+ """Create mock local Claude settings and patch HOME to use them.
84+
85+ Args:
86+ tmp_path: Temporary directory to create mock home in
87+ content: Settings content (string or dict to be JSON-serialized)
88+
89+ Yields:
90+ Path to the local settings file (for verification after install)
91+ """
92+ mock_home = tmp_path / "mock_home"
93+ mock_local_claude_dir = mock_home / ".claude"
94+ mock_local_claude_dir .mkdir (parents = True )
95+
96+ local_settings_file = mock_local_claude_dir / "settings.json"
97+ if isinstance (content , dict ):
98+ local_settings_file .write_text (json .dumps (content , indent = 2 ))
99+ else :
100+ local_settings_file .write_text (content )
101+
102+ with patch .dict ("os.environ" , {"HOME" : str (mock_home )}):
103+ yield local_settings_file
104+
105+
77106# =============================================================================
78107# REQ-001: Install MUST NOT modify local (user home) Claude settings
79108# =============================================================================
@@ -114,44 +143,30 @@ def test_install_does_not_modify_local_claude_settings(
114143
115144 DO NOT MODIFY THIS TEST.
116145 """
117- # Create a mock local Claude settings directory in tmp_path
118- mock_home = tmp_path / "mock_home"
119- mock_local_claude_dir = mock_home / ".claude"
120- mock_local_claude_dir .mkdir (parents = True )
121-
122- # Create local settings with known content
123- local_settings_file = mock_local_claude_dir / "settings.json"
124146 original_local_settings = {
125147 "user_preference" : "do_not_change" ,
126148 "api_key_encrypted" : "sensitive_data_here" ,
127149 "custom_config" : {"setting1" : True , "setting2" : "value" },
128150 }
129- local_settings_file .write_text (json .dumps (original_local_settings , indent = 2 ))
130151
131- # Record the original file modification time
132- original_mtime = local_settings_file .stat ().st_mtime
133-
134- # Run install with mocked home directory
135- with patch .dict ("os.environ" , {"HOME" : str (mock_home )}):
152+ with mock_local_claude_settings (tmp_path , original_local_settings ) as local_file :
153+ original_mtime = local_file .stat ().st_mtime
136154 run_install (mock_claude_project )
137155
138- # CRITICAL: Verify local settings were NOT modified
139- assert local_settings_file .exists (), "Local settings file should still exist"
156+ # CRITICAL: Verify local settings were NOT modified
157+ assert local_file .exists (), "Local settings file should still exist"
140158
141- # Check content is unchanged
142- current_local_settings = json .loads (local_settings_file .read_text ())
143- assert current_local_settings == original_local_settings , (
144- "LOCAL SETTINGS WERE MODIFIED! "
145- "Install MUST NOT touch user's home directory Claude settings. "
146- f"Expected: { original_local_settings } , Got: { current_local_settings } "
147- )
159+ current_local_settings = json .loads (local_file .read_text ())
160+ assert current_local_settings == original_local_settings , (
161+ "LOCAL SETTINGS WERE MODIFIED! "
162+ "Install MUST NOT touch user's home directory Claude settings. "
163+ f"Expected: { original_local_settings } , Got: { current_local_settings } "
164+ )
148165
149- # Check modification time is unchanged
150- current_mtime = local_settings_file .stat ().st_mtime
151- assert current_mtime == original_mtime , (
152- "LOCAL SETTINGS FILE WAS TOUCHED! "
153- "Install MUST NOT access user's home directory Claude settings."
154- )
166+ assert local_file .stat ().st_mtime == original_mtime , (
167+ "LOCAL SETTINGS FILE WAS TOUCHED! "
168+ "Install MUST NOT access user's home directory Claude settings."
169+ )
155170
156171 def test_install_only_modifies_project_settings (
157172 self , mock_claude_project : Path , tmp_path : Path
@@ -164,27 +179,19 @@ def test_install_only_modifies_project_settings(
164179
165180 DO NOT MODIFY THIS TEST.
166181 """
167- # Create mock local Claude settings
168- mock_home = tmp_path / "mock_home"
169- mock_local_claude_dir = mock_home / ".claude"
170- mock_local_claude_dir .mkdir (parents = True )
171-
172- local_settings_file = mock_local_claude_dir / "settings.json"
173182 original_local_content = '{"local": "unchanged"}'
174- local_settings_file .write_text (original_local_content )
175183
176- # Run install
177- with patch .dict ("os.environ" , {"HOME" : str (mock_home )}):
184+ with mock_local_claude_settings (tmp_path , original_local_content ) as local_file :
178185 run_install (mock_claude_project )
179186
180- # Verify LOCAL settings unchanged
181- assert local_settings_file .read_text () == original_local_content , (
182- "Local settings were modified! Install must only modify project settings."
183- )
187+ # Verify LOCAL settings unchanged
188+ assert local_file .read_text () == original_local_content , (
189+ "Local settings were modified! Install must only modify project settings."
190+ )
184191
185- # Verify PROJECT settings were modified (hooks should be added)
186- project_settings = get_project_settings (mock_claude_project )
187- assert "hooks" in project_settings , "Project settings should have hooks after install"
192+ # Verify PROJECT settings were modified (hooks should be added)
193+ project_settings = get_project_settings (mock_claude_project )
194+ assert "hooks" in project_settings , "Project settings should have hooks after install"
188195
189196
190197# =============================================================================
0 commit comments