Skip to content

Commit 3c9c261

Browse files
committed
fix(tests): mock shutil.which so existing tests pass without sf installed
The previous commit replaced shell=True + "sf" string with shutil.which() to resolve the sf executable path before calling subprocess.run. This moved the "sf not found" check earlier — before _run_sf_command is even defined — meaning tests that only mocked subprocess.run were now hitting the shutil.which() call for real. On CI (Linux, no SF CLI), which() returns None and raises immediately, causing all 11 SFCLITokenProvider and SFCLIDataCloudReader tests to fail with "sf command was not found" rather than exercising the error condition they were testing. Fix: mock shutil.which alongside subprocess.run in each affected test, using the same unittest.mock.patch pattern already used throughout the test suite. return_value="sf" keeps the resolved path consistent with the hardcoded "sf" string the command-list assertions in test_sf_cli.py expect. The "sf not found" tests are updated to patch shutil.which returning None instead of patching subprocess.run with FileNotFoundError, since that FileNotFoundError path was removed from the production code. No new test files, no new imports, no new patterns — all changes are confined to existing test classes and follow the same nesting style used in TestCredentialsTokenProvider and the rest of the suite.
1 parent 6ee5618 commit 3c9c261

2 files changed

Lines changed: 30 additions & 23 deletions

File tree

tests/io/reader/test_sf_cli.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ class TestGetToken:
7373
def reader(self):
7474
return _make_reader()
7575

76+
@pytest.fixture(autouse=True)
77+
def mock_sf_which(self):
78+
with patch("shutil.which", return_value="sf"):
79+
yield
80+
7681
def _run_result(self, stdout: str) -> MagicMock:
7782
result = MagicMock()
7883
result.stdout = stdout
@@ -118,7 +123,7 @@ def test_returns_token_and_instance_url(self, reader):
118123
)
119124

120125
def test_file_not_found_raises_runtime_error(self, reader):
121-
with patch("subprocess.run", side_effect=FileNotFoundError):
126+
with patch("shutil.which", return_value=None):
122127
with pytest.raises(RuntimeError, match="'sf' command was not found"):
123128
reader._get_token()
124129

tests/test_token_provider.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -178,24 +178,25 @@ def test_successful_token_retrieval(self):
178178
}
179179
)
180180

181-
with patch("subprocess.run") as mock_run:
182-
mock_run.side_effect = [
183-
MagicMock(stdout=display_output),
184-
MagicMock(stdout=token_output),
185-
]
181+
with patch("shutil.which", return_value="sf"):
182+
with patch("subprocess.run") as mock_run:
183+
mock_run.side_effect = [
184+
MagicMock(stdout=display_output),
185+
MagicMock(stdout=token_output),
186+
]
186187

187-
result = provider.get_token()
188+
result = provider.get_token()
188189

189-
assert isinstance(result, AccessTokenResponse)
190-
assert result.access_token == "cli_access_token"
191-
assert result.instance_url == "https://cli.salesforce.com"
190+
assert isinstance(result, AccessTokenResponse)
191+
assert result.access_token == "cli_access_token"
192+
assert result.instance_url == "https://cli.salesforce.com"
192193

193-
# Verify both commands were called
194-
assert mock_run.call_count == 2
195-
display_call = mock_run.call_args_list[0]
196-
token_call = mock_run.call_args_list[1]
197-
assert "org" in display_call[0][0] and "display" in display_call[0][0]
198-
assert "show-access-token" in token_call[0][0]
194+
# Verify both commands were called
195+
assert mock_run.call_count == 2
196+
display_call = mock_run.call_args_list[0]
197+
token_call = mock_run.call_args_list[1]
198+
assert "org" in display_call[0][0] and "display" in display_call[0][0]
199+
assert "show-access-token" in token_call[0][0]
199200

200201
def test_fallback_to_display_token_on_older_cli(self):
201202
"""Test fallback to sf org display token when show-access-token unavailable."""
@@ -224,17 +225,18 @@ def side_effect(*args, **kwargs):
224225
mock.stdout = display_output
225226
return mock
226227

227-
with patch("subprocess.run", side_effect=side_effect):
228-
result = provider.get_token()
228+
with patch("shutil.which", return_value="sf"):
229+
with patch("subprocess.run", side_effect=side_effect):
230+
result = provider.get_token()
229231

230-
assert isinstance(result, AccessTokenResponse)
231-
assert result.access_token == "display_token"
232-
assert result.instance_url == "https://cli.salesforce.com"
232+
assert isinstance(result, AccessTokenResponse)
233+
assert result.access_token == "display_token"
234+
assert result.instance_url == "https://cli.salesforce.com"
233235

234236
def test_sf_command_not_found(self):
235-
"""Test that FileNotFoundError is wrapped in RuntimeError."""
237+
"""Test that a missing sf binary raises RuntimeError."""
236238
provider = SFCLITokenProvider("test_org")
237239

238-
with patch("subprocess.run", side_effect=FileNotFoundError()):
240+
with patch("shutil.which", return_value=None):
239241
with pytest.raises(RuntimeError, match="'sf' command was not found"):
240242
provider.get_token()

0 commit comments

Comments
 (0)