afv-library/plugins/builder/salesforce-development/scripts/test/test_sf_context.py

1070 lines
57 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Unit tests for the cross-platform executable resolver in sf_context.py
(WIN-026) and the deterministic setup/org reporting (WIN-027).
These are the evidence for the Windows fix: they simulate Windows resolution of a
`.cmd`/`.bat` shim (via a faked `shutil.which`) and assert that a COMSPEC-wrapped
ARGV ARRAY is built — never a shell string — while POSIX paths spawn directly.
They also assert that a genuinely-missing tool is reported FAILED (not silently
empty, not green) and that failure diagnostics never leak tokens/secrets.
Offline: no live org, no real subprocess spawn (subprocess.run / shutil.which are
mocked). Stdlib unittest only (no pytest/PyYAML) so it runs anywhere Python does,
including the 3.9 baseline.
Run: python3 plugins/builder/salesforce-development/scripts/test/test_sf_context.py
"""
from __future__ import annotations
import importlib.util
import json
import io
import types
import unittest
from contextlib import redirect_stdout
from pathlib import Path
from unittest import mock
# sf_context.py is the sibling of this test's parent dir: scripts/test/ → scripts/.
# (The runtime lives under scripts/ rather than bin/ because this repo's
# .gitignore blocks bin/ — see the bin/README.md note.)
_MODULE_PATH = Path(__file__).resolve().parent.parent / "sf_context.py"
def _load_module():
spec = importlib.util.spec_from_file_location("sf_context_under_test", _MODULE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module
sfx = _load_module()
def _completed(stdout="", returncode=0, stderr=""):
"""A stand-in for subprocess.CompletedProcess (only the fields run() reads)."""
return types.SimpleNamespace(stdout=stdout, returncode=returncode, stderr=stderr)
class ResolveExecutableTests(unittest.TestCase):
def test_delegates_to_shutil_which(self):
with mock.patch.object(sfx.shutil, "which", return_value="/usr/local/bin/sf") as which:
self.assertEqual(sfx.resolve_executable("sf"), "/usr/local/bin/sf")
which.assert_called_once_with("sf")
def test_windows_shim_found_via_pathext(self):
# shutil.which honors PATHEXT on Windows, so a bare "sf" resolves to sf.cmd.
with mock.patch.object(sfx.shutil, "which", return_value=r"C:\tools\sf\bin\sf.cmd"):
self.assertEqual(sfx.resolve_executable("sf"), r"C:\tools\sf\bin\sf.cmd")
def test_missing_returns_none(self):
with mock.patch.object(sfx.shutil, "which", return_value=None):
self.assertIsNone(sfx.resolve_executable("definitely-not-a-tool"))
def test_empty_name_returns_none(self):
self.assertIsNone(sfx.resolve_executable(""))
class BuildCommandTests(unittest.TestCase):
def test_posix_spawns_resolved_path_directly(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"):
argv = sfx.build_command("sf", ["config", "get", "target-org", "--json"])
self.assertEqual(argv, ["/usr/local/bin/sf", "config", "get", "target-org", "--json"])
# A plain argv array, first element the resolved binary (no cmd wrapper).
self.assertIsInstance(argv, list)
self.assertNotIn("/c", argv)
def test_windows_cmd_shim_wrapped_with_comspec(self):
resolved = r"C:\Program Files\sf\bin\sf.cmd"
with mock.patch.object(sfx, "resolve_executable", return_value=resolved), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": r"C:\Windows\System32\cmd.exe"}, clear=False):
argv = sfx.build_command("sf", ["config", "get", "target-org"])
self.assertEqual(
argv,
[r"C:\Windows\System32\cmd.exe", "/c", resolved, "config", "get", "target-org"],
)
def test_windows_bat_shim_wrapped(self):
resolved = r"C:\tools\npm.bat"
with mock.patch.object(sfx, "resolve_executable", return_value=resolved), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": r"C:\Windows\System32\cmd.exe"}, clear=False):
argv = sfx.build_command("npm", ["--version"])
self.assertEqual(argv, [r"C:\Windows\System32\cmd.exe", "/c", resolved, "--version"])
def test_comspec_falls_back_to_cmd_exe(self):
resolved = r"C:\tools\sf.cmd"
env_without_comspec = {k: v for k, v in sfx.os.environ.items() if k != "COMSPEC"}
with mock.patch.object(sfx, "resolve_executable", return_value=resolved), \
mock.patch.dict(sfx.os.environ, env_without_comspec, clear=True):
argv = sfx.build_command("sf", ["version"])
self.assertEqual(argv, ["cmd.exe", "/c", resolved, "version"])
def test_missing_tool_returns_none(self):
with mock.patch.object(sfx, "resolve_executable", return_value=None):
self.assertIsNone(sfx.build_command("sf", ["version"]))
def test_never_builds_a_shell_string(self):
# The crux of the .cmd case: "no shell" and "injection-safe" are reconciled
# by keeping an ARGV ARRAY. Assert the result is always a list of tokens,
# never a single concatenated command string.
for resolved in (r"C:\tools\sf.cmd", "/usr/local/bin/sf"):
with mock.patch.object(sfx, "resolve_executable", return_value=resolved):
argv = sfx.build_command("sf", ["config", "get"])
self.assertIsInstance(argv, list)
for token in argv:
self.assertIsInstance(token, str)
def test_cmd_shim_refuses_metacharacter_args(self):
# cmd.exe re-parses its command line, so an arg with a shell metacharacter
# must NOT reach a batch shim. build_command fails closed (returns None).
for bad in ("safe&whoami", "a|b", "x>y", "a<b", "p^q", "%PATH%", 'a"b',
"a!b", "a(b", "a)b", "a\nb", "a\rb"):
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False):
argv = sfx.build_command("sf", ["config", "get", bad])
self.assertIsNone(argv, f"metacharacter arg should be refused: {bad!r}")
def test_cmd_shim_refuses_dangerous_path(self):
# A reparse-dangerous char in the resolved shim PATH is also refused
# (e.g. `%` env-expansion or unquoted `&`), fail closed.
for bad_path in (r"C:\a&b\sf.cmd", r"C:\weird%dir\sf.cmd", r"C:\x!y\sf.cmd"):
with mock.patch.object(sfx, "resolve_executable", return_value=bad_path), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False):
self.assertIsNone(sfx.build_command("sf", ["version"]),
f"dangerous shim path should be refused: {bad_path!r}")
def test_cmd_shim_allows_program_files_x86_path(self):
# `(` `)` appear in legitimate install paths, so the PATH guard must NOT
# reject them (they're only rejected in ARGS).
resolved = r"C:\Program Files (x86)\sf\bin\sf.cmd"
with mock.patch.object(sfx, "resolve_executable", return_value=resolved), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False):
argv = sfx.build_command("sf", ["version"])
self.assertEqual(argv, ["cmd.exe", "/c", resolved, "version"])
def test_posix_path_allows_metacharacters(self):
# The reparse hazard is cmd.exe-specific; a direct shell=False spawn of a
# POSIX/.exe path is not subject to it, so args pass through unchanged.
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"):
argv = sfx.build_command("sf", ["config", "get", "safe&whoami"])
self.assertEqual(argv, ["/usr/local/bin/sf", "config", "get", "safe&whoami"])
def test_cmd_shim_allows_ordinary_args(self):
# A normal alias/flag arg (no metacharacters, spaces ok) still runs.
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False):
argv = sfx.build_command("sf", ["org", "display", "--target-org", "my-scratch"])
self.assertEqual(argv, ["cmd.exe", "/c", r"C:\tools\sf.cmd", "org", "display", "--target-org", "my-scratch"])
def test_run_refuses_metacharacter_arg_and_never_spawns(self):
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False), \
mock.patch.object(sfx.subprocess, "run") as spawn:
self.assertEqual(sfx.run(["sf", "config", "get", "a&b"]), "")
spawn.assert_not_called()
def test_preserves_argv_boundaries(self):
# A SOQL query with spaces must remain ONE argv element (no concatenation),
# both on POSIX and inside the cmd wrapper.
query = "SELECT Id FROM Account WHERE Name = 'Acme Inc'"
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"):
argv = sfx.build_command("sf", ["data", "query", "--query", query])
self.assertIn(query, argv)
self.assertEqual(argv[-1], query)
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False):
argv = sfx.build_command("sf", ["data", "query", "--query", query])
self.assertEqual(argv[-1], query)
self.assertEqual(argv[:2], ["cmd.exe", "/c"])
class RunTests(unittest.TestCase):
def test_run_spawns_comspec_argv_with_shell_false(self):
resolved = r"C:\tools\sf.cmd"
captured = {}
def fake_run(argv, **kwargs):
captured["argv"] = argv
captured["kwargs"] = kwargs
return _completed(stdout="ok-out", returncode=0)
with mock.patch.object(sfx, "resolve_executable", return_value=resolved), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False), \
mock.patch.object(sfx.subprocess, "run", side_effect=fake_run):
out = sfx.run(["sf", "config", "get", "target-org", "--json"])
self.assertEqual(out, "ok-out")
self.assertEqual(captured["argv"], ["cmd.exe", "/c", resolved, "config", "get", "target-org", "--json"])
self.assertIsInstance(captured["argv"], list)
self.assertIs(captured["kwargs"].get("shell"), False)
def test_run_posix_spawns_resolved_path(self):
captured = {}
def fake_run(argv, **kwargs):
captured["argv"] = argv
return _completed(stdout="v1", returncode=0)
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/git"), \
mock.patch.object(sfx.subprocess, "run", side_effect=fake_run):
out = sfx.run(["git", "--version"])
self.assertEqual(out, "v1")
self.assertEqual(captured["argv"], ["/usr/local/bin/git", "--version"])
def test_run_missing_tool_returns_empty_and_never_spawns(self):
with mock.patch.object(sfx, "resolve_executable", return_value=None), \
mock.patch.object(sfx.subprocess, "run") as spawn:
self.assertEqual(sfx.run(["sf", "version"]), "")
spawn.assert_not_called()
def test_run_nonzero_returncode_returns_empty(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run", return_value=_completed(stdout="x", returncode=1)):
self.assertEqual(sfx.run(["sf", "version"]), "")
def test_run_timeout_returns_empty(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
side_effect=sfx.subprocess.TimeoutExpired(cmd="sf", timeout=1)):
self.assertEqual(sfx.run(["sf", "version"]), "")
def test_run_applies_platform_default_timeout(self):
# An unspecified timeout resolves to the platform-aware _cli_timeout()
# (longer on Windows to survive slow cold `sf.cmd` startup under load).
captured = {}
def fake_run(argv, **kwargs):
captured["timeout"] = kwargs.get("timeout")
return _completed(stdout="x", returncode=0)
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.object(sfx, "_is_windows", return_value=True), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False), \
mock.patch.object(sfx.subprocess, "run", side_effect=fake_run):
sfx.run(["sf", "config", "get", "target-org"])
self.assertEqual(captured["timeout"], 30)
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx, "_is_windows", return_value=False), \
mock.patch.object(sfx.subprocess, "run", side_effect=fake_run):
sfx.run(["sf", "version"])
self.assertEqual(captured["timeout"], 10)
class PlatformTuningTests(unittest.TestCase):
def test_cli_timeout_scales_on_windows(self):
with mock.patch.object(sfx, "_is_windows", return_value=True):
self.assertEqual(sfx._cli_timeout(), 30)
with mock.patch.object(sfx, "_is_windows", return_value=False):
self.assertEqual(sfx._cli_timeout(), 10)
def test_check_tools_workers_reduced_on_windows(self):
with mock.patch.object(sfx, "_is_windows", return_value=True):
self.assertEqual(sfx._check_tools_workers(), 3)
with mock.patch.object(sfx, "_is_windows", return_value=False):
self.assertEqual(sfx._check_tools_workers(), 7)
class ForceUtf8StdioTests(unittest.TestCase):
def test_reconfigures_stdout_and_stderr_to_utf8(self):
# Windows cp1252 consoles can't encode the box-drawing glyphs the status
# commands print; startup reconfigures the streams to UTF-8.
calls = []
class FakeStream:
def reconfigure(self, **kw):
calls.append(kw)
with mock.patch.object(sfx.sys, "stdout", FakeStream()), \
mock.patch.object(sfx.sys, "stderr", FakeStream()):
sfx._force_utf8_stdio()
self.assertEqual(calls, [{"encoding": "utf-8"}, {"encoding": "utf-8"}])
def test_stream_without_reconfigure_is_safe(self):
class NoReconfigure:
pass
with mock.patch.object(sfx.sys, "stdout", NoReconfigure()), \
mock.patch.object(sfx.sys, "stderr", NoReconfigure()):
sfx._force_utf8_stdio() # must not raise
def test_reconfigure_error_is_swallowed(self):
class BadStream:
def reconfigure(self, **kw):
raise ValueError("boom")
with mock.patch.object(sfx.sys, "stdout", BadStream()), \
mock.patch.object(sfx.sys, "stderr", BadStream()):
sfx._force_utf8_stdio() # must not raise
class GetTargetOrgTests(unittest.TestCase):
_CONFIG_JSON = json.dumps(
{"result": [{"name": "target-org", "value": "myScratch"}]}
)
_NO_ORG_JSON = json.dumps({"result": [{"name": "target-org"}]})
def setUp(self):
# get_target_org_detailed now honors SF_TARGET_ORG / SFDX_TARGET_ORG before
# the CLI config (matching `sf` and the proxy). Scrub them so the CLI-mock
# cases below observe the config path, not the runner's ambient env.
patcher = mock.patch.dict(sfx.os.environ, {}, clear=False)
patcher.start()
self.addCleanup(patcher.stop)
sfx.os.environ.pop("SF_TARGET_ORG", None)
sfx.os.environ.pop("SFDX_TARGET_ORG", None)
def test_succeeds_when_sf_resolves_to_cmd_shim(self):
# The Windows regression: get_target_org() returned "" because sf.cmd
# could not be launched. With the resolver it must succeed.
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=self._CONFIG_JSON, returncode=0)):
self.assertEqual(sfx.get_target_org(), "myScratch")
def test_missing_cli_reports_no_org_not_a_crash(self):
with mock.patch.object(sfx, "resolve_executable", return_value=None):
self.assertEqual(sfx.get_target_org(), "")
def test_detailed_distinguishes_no_org_from_cli_failure(self):
# CLI ran, org set → (alias, "").
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=self._CONFIG_JSON, returncode=0)):
self.assertEqual(sfx.get_target_org_detailed(), ("myScratch", ""))
# CLI ran, no org set → ("", "") (empty reason = genuinely no org).
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=self._NO_ORG_JSON, returncode=0)):
self.assertEqual(sfx.get_target_org_detailed(), ("", ""))
# CLI present but query failed (nonzero) → ("", "nonzero"), NOT a false no-org.
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout="", returncode=1)):
alias, reason = sfx.get_target_org_detailed()
self.assertEqual(alias, "")
self.assertEqual(reason, "nonzero")
# CLI query timed out → ("", "timeout").
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
side_effect=sfx.subprocess.TimeoutExpired(cmd="sf", timeout=1)):
self.assertEqual(sfx.get_target_org_detailed(), ("", "timeout"))
def test_detailed_flags_invalid_output_on_exit_zero(self):
# sf exits 0 but the payload can't be trusted → "invalid-output", never a
# false "no org configured" and never a crash on an unexpected shape.
cases = {
"malformed JSON": "{not valid json",
"empty stdout": "",
"non-object root (array)": "[1, 2, 3]",
"non-object root (scalar)": "\"hi\"",
"missing result field": json.dumps({"status": 0}),
"result not a list (object)": json.dumps({"result": {}}),
"result not a list (string)": json.dumps({"result": "x"}),
}
for label, payload in cases.items():
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=payload, returncode=0)):
alias, reason = sfx.get_target_org_detailed()
self.assertEqual(alias, "", f"{label}: alias should be empty")
self.assertEqual(reason, "invalid-output", f"{label}: reason should be invalid-output")
def test_detailed_well_formed_no_entry_is_no_org(self):
# A well-formed empty/other-key result is genuinely "no org" (not invalid),
# and non-dict entries in the list don't crash.
for payload in (json.dumps({"result": []}),
json.dumps({"result": [{"name": "other"}]}),
json.dumps({"result": ["stringy", 5, None]})):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=payload, returncode=0)):
self.assertEqual(sfx.get_target_org_detailed(), ("", ""))
def test_env_target_org_takes_precedence_over_config(self):
# SF_TARGET_ORG / SFDX_TARGET_ORG must win over the CLI config, matching
# how `sf` itself and the proxy's resolveTargetOrg() resolve the org. If
# the consumer read config here while the proxy stamped sidecars with the
# env org, the MCP-health filter would reject valid sidecars.
for var in ("SF_TARGET_ORG", "SFDX_TARGET_ORG"):
with self.subTest(var=var):
# The CLI would say "myScratch"; the env override must win — and
# subprocess.run must not even be consulted (short-circuit).
run_spy = mock.MagicMock(
return_value=_completed(stdout=self._CONFIG_JSON, returncode=0))
with mock.patch.dict(sfx.os.environ, {var: "envOrg"}, clear=False), \
mock.patch.object(sfx.subprocess, "run", run_spy):
self.assertEqual(sfx.get_target_org_detailed(), ("envOrg", ""))
run_spy.assert_not_called()
def test_sf_target_org_wins_over_sfdx_target_org(self):
# When both are set, SF_TARGET_ORG takes priority (same order as the proxy).
with mock.patch.dict(sfx.os.environ,
{"SF_TARGET_ORG": "sfOrg", "SFDX_TARGET_ORG": "sfdxOrg"},
clear=False):
self.assertEqual(sfx.get_target_org_detailed(), ("sfOrg", ""))
class RunResultTests(unittest.TestCase):
def test_ok_result(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout="hi", returncode=0)):
res = sfx.run_result(["sf", "version"])
self.assertTrue(res.ok)
self.assertEqual(res.stdout, "hi")
self.assertEqual(res.reason, "")
def test_unresolved_reason(self):
with mock.patch.object(sfx, "resolve_executable", return_value=None):
res = sfx.run_result(["sf", "version"])
self.assertFalse(res.ok)
self.assertEqual(res.reason, "unresolved")
def test_nonzero_reason(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout="", returncode=2)):
res = sfx.run_result(["sf", "version"])
self.assertFalse(res.ok)
self.assertEqual(res.reason, "nonzero")
self.assertEqual(res.returncode, 2)
def test_timeout_reason(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
side_effect=sfx.subprocess.TimeoutExpired(cmd="sf", timeout=1)):
self.assertEqual(sfx.run_result(["sf", "version"]).reason, "timeout")
def test_run_wrapper_preserves_empty_on_failure(self):
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout="junk", returncode=1)):
self.assertEqual(sfx.run(["sf", "version"]), "")
class CheckToolsTests(unittest.TestCase):
def test_sf_cli_ok_when_cmd_shim_resolves(self):
version_out = "@salesforce/cli/2.100.0 win32-x64 node-v20.0.0"
with mock.patch.object(sfx, "resolve_executable", return_value=r"C:\tools\sf.cmd"), \
mock.patch.dict(sfx.os.environ, {"COMSPEC": "cmd.exe"}, clear=False), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=version_out, returncode=0)):
result = sfx._check_sf_cli()
self.assertEqual(result["status"], "ok")
self.assertEqual(result["version"], "2.100.0")
def test_sf_cli_warns_when_update_available(self):
# Readiness = latest. The cached oclif notice (on `sf version` stderr)
# reports a newer release, so an installed-but-outdated CLI is 🟡, not 🟢.
version_out = "@salesforce/cli/2.130.9 darwin-arm64 node-v22.0.0"
stderr_out = " Warning: @salesforce/cli update available from 2.130.9 to 2.144.6."
env = {k: v for k, v in sfx.os.environ.items() if k != sfx._UPDATE_CHECK_ENV}
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.dict(sfx.os.environ, env, clear=True), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=version_out, stderr=stderr_out, returncode=0)):
result = sfx._check_sf_cli()
self.assertEqual(result["status"], "warn")
self.assertEqual(result["version"], "2.130.9")
self.assertIn("2.144.6", result["message"])
def test_sf_cli_ok_when_up_to_date(self):
# No update notice on stderr → the CLI is current → 🟢.
version_out = "@salesforce/cli/2.144.6 darwin-arm64 node-v22.0.0"
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=version_out, stderr="", returncode=0)):
result = sfx._check_sf_cli()
self.assertEqual(result["status"], "ok")
self.assertEqual(result["version"], "2.144.6")
def test_sf_cli_update_check_opt_out_stays_ok(self):
# SFDX_SKIP_CLI_UPDATE_CHECK=1 disables the readiness warn even when an
# update notice is present — a user who opted out never sees the 🟡.
version_out = "@salesforce/cli/2.130.9 darwin-arm64 node-v22.0.0"
stderr_out = " Warning: @salesforce/cli update available from 2.130.9 to 2.144.6."
with mock.patch.object(sfx, "resolve_executable", return_value="/usr/local/bin/sf"), \
mock.patch.dict(sfx.os.environ, {sfx._UPDATE_CHECK_ENV: "1"}, clear=False), \
mock.patch.object(sfx.subprocess, "run",
return_value=_completed(stdout=version_out, stderr=stderr_out, returncode=0)):
result = sfx._check_sf_cli()
self.assertEqual(result["status"], "ok")
def test_missing_sf_cli_reported_critical_not_silently_empty(self):
# A genuinely-missing tool: resolver finds nothing, run() returns "".
# WIN-027: this must be reported FAILED, never silently empty or green.
with mock.patch.object(sfx, "resolve_executable", return_value=None):
result = sfx._check_sf_cli()
self.assertEqual(result["status"], "critical")
self.assertNotEqual(result["status"], "ok")
self.assertIn("Not found", result["message"])
def test_missing_npm_reported_critical(self):
with mock.patch.object(sfx, "resolve_executable", return_value=None):
result = sfx._check_npm()
self.assertEqual(result["status"], "critical")
def test_mcp_check_keeps_three_concerns_distinct(self):
# WIN-027: config presence, per-server platform-MCP health (WIN-033/040),
# and process health are separate rows; process health is NEVER inferred
# green from config.
with mock.patch.object(sfx, "_probe_server",
side_effect=lambda slug, timeout=None: {
"name": sfx._mcp_row_name(slug), "status": "warn",
"version": None, "message": "stubbed"}):
rows = sfx._check_mcp()
names = [r["name"] for r in rows]
self.assertIn("Salesforce MCP (config)", names)
self.assertIn("Salesforce MCP (salesforce-api-context)", names)
self.assertIn("Salesforce MCP (metadata-experts)", names)
self.assertIn("Salesforce MCP (process)", names)
process_row = next(r for r in rows if r["name"] == "Salesforce MCP (process)")
# Process health is informational (not a warning), so a healthy setup can
# read fully green — but it is never inferred "ok" from config/endpoint.
self.assertEqual(process_row["status"], "info")
self.assertNotEqual(process_row["status"], "ok")
def test_code_analyzer_installed_reports_ok(self):
# Physically installed → `sf plugins inspect` returns a real version.
inspect_out = json.dumps([{"name": "@salesforce/plugin-code-analyzer", "version": "5.11.1"}])
def fake_run(argv, **_):
if "inspect" in argv:
return inspect_out
return ""
with mock.patch.object(sfx, "run", side_effect=fake_run):
result = sfx._check_code_analyzer()
self.assertEqual(result["status"], "ok")
self.assertEqual(result["version"], "5.11.1")
self.assertEqual(result["message"], "Installed")
def test_code_analyzer_jit_registered_reports_ok_not_critical(self):
# NOT physically installed → `inspect` fails (JIT plugins yield {"error": {}}
# and exit 1, so run() returns ""). But the CLI registers it as a JIT plugin,
# so it auto-installs on first use — this is AVAILABLE, never critical.
plugins_out = json.dumps([
{"name": "@salesforce/plugin-org", "version": "3.0.0"},
{
"name": "@salesforce/cli",
"options": {"isRoot": True},
"pjson": {"oclif": {"jitPlugins": {"@salesforce/plugin-code-analyzer": "5.11.1"}}},
},
])
def fake_run(argv, **_):
if "inspect" in argv:
return "" # JIT plugin not yet installed
if "--json" in argv:
return plugins_out
return ""
with mock.patch.object(sfx, "run", side_effect=fake_run):
result = sfx._check_code_analyzer()
self.assertEqual(result["status"], "ok")
self.assertNotEqual(result["status"], "critical")
self.assertEqual(result["version"], "5.11.1")
self.assertIn("JIT", result["message"])
def test_code_analyzer_genuinely_absent_reports_critical(self):
# inspect fails AND the plugin is not in the CLI's jitPlugins registry →
# genuinely missing, so report critical with the install hint.
plugins_out = json.dumps([
{"name": "@salesforce/cli", "options": {"isRoot": True},
"pjson": {"oclif": {"jitPlugins": {"@salesforce/plugin-signups": "2.0.0"}}}},
])
def fake_run(argv, **_):
if "inspect" in argv:
return ""
if "--json" in argv:
return plugins_out
return ""
with mock.patch.object(sfx, "run", side_effect=fake_run):
result = sfx._check_code_analyzer()
self.assertEqual(result["status"], "critical")
self.assertIsNone(result["version"])
self.assertIn("plugins install", result["message"])
def test_check_tools_attaches_diagnostic_on_failure(self):
# All native tools missing → several critical rows → a diagnostic block is
# attached so the failure is understandable (and not flipped green).
with mock.patch.object(sfx, "resolve_executable", return_value=None), \
mock.patch.object(sfx, "get_target_org", return_value=""):
buf = io.StringIO()
with redirect_stdout(buf):
sfx.cmd_check_tools()
report = json.loads(buf.getvalue())
self.assertIn("tools", report)
self.assertTrue(any(t["status"] == "critical" for t in report["tools"]))
self.assertIn("diagnostic", report)
diag = report["diagnostic"]
for key in ("platform", "shell", "cwd", "pluginRoot", "resolvedExecutables"):
self.assertIn(key, diag)
class McpHealthContractTests(unittest.TestCase):
"""WIN-033 (passive sidecar read) + WIN-040 (active --probe) — see
CONTRACT-mcp-health.md. The consumer owns the server-key -> slug-arg
mapping; sidecar filename AND --probe arg both use the SLUG ARG
("metadata-experts"), never the .mcp.json server key
("salesforce-metadata-experts")."""
def test_slug_mapping_uses_slug_arg_not_server_key(self):
self.assertEqual(sfx._MCP_SERVER_SLUGS["salesforce-api-context"], "salesforce-api-context")
self.assertEqual(sfx._MCP_SERVER_SLUGS["salesforce-metadata-experts"], "metadata-experts")
self.assertNotIn("salesforce-metadata-experts", sfx._MCP_SERVER_SLUGS.values())
def test_state_table_matches_contract(self):
self.assertEqual(sfx._render_mcp_state_row("s", "ok")["status"], "ok")
self.assertEqual(sfx._render_mcp_state_row("s", "inactive")["status"], "critical")
self.assertEqual(sfx._render_mcp_state_row("s", "auth")["status"], "warn")
self.assertEqual(sfx._render_mcp_state_row("s", "env-not-ready")["status"], "warn")
self.assertEqual(sfx._render_mcp_state_row("s", "unreachable")["status"], "warn")
def test_unknown_state_renders_neutral_warn_not_crash(self):
row = sfx._render_mcp_state_row("metadata-experts", "some-future-state")
self.assertEqual(row["status"], "warn")
self.assertIn("Unrecognized", row["message"])
def test_missing_state_renders_neutral_warn_not_crash(self):
row = sfx._render_mcp_state_row("metadata-experts", None)
self.assertEqual(row["status"], "warn")
def test_passive_row_absent_sidecar_is_neutral_not_invented(self):
with mock.patch.object(sfx, "_read_health_sidecar", return_value=None):
row = sfx._passive_mcp_row("metadata-experts")
self.assertEqual(row["status"], "info")
self.assertIn("not yet observed", row["message"].lower())
def test_passive_row_present_sidecar_renders_from_state(self):
with mock.patch.object(sfx, "_read_health_sidecar",
return_value={"slug": "metadata-experts", "state": "inactive",
"detail": "HTTP 404 Server definition not found"}):
row = sfx._passive_mcp_row("metadata-experts")
self.assertEqual(row["status"], "critical")
self.assertIn("not activated", row["message"])
def test_read_health_sidecar_reads_slug_named_file(self):
# The sidecar path MUST be keyed by the slug arg, not the server key.
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(Path, "read_text", return_value=json.dumps({"state": "ok"})) as read_text:
data = sfx._read_health_sidecar("metadata-experts")
self.assertEqual(data, {"state": "ok"})
# read_text was called on a Path ending in metadata-experts.json.
self.assertTrue(read_text.call_count >= 1)
def test_read_health_sidecar_bad_json_returns_none_not_crash(self):
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(Path, "read_text", return_value="{not valid json"):
self.assertIsNone(sfx._read_health_sidecar("metadata-experts"))
def test_read_health_sidecar_missing_file_returns_none(self):
with mock.patch.object(Path, "exists", return_value=False):
self.assertIsNone(sfx._read_health_sidecar("metadata-experts"))
def test_probe_server_parses_json_line_from_stdout(self):
probe_json = json.dumps({"slug": "metadata-experts", "state": "auth",
"detail": "401", "httpStatus": 401, "org": "my-alias"})
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(sfx, "run_result",
return_value=sfx.RunResult(True, probe_json, 0, "")):
row = sfx._probe_server("metadata-experts")
self.assertEqual(row["status"], "warn")
self.assertEqual(row["name"], "Salesforce MCP (metadata-experts)")
def test_probe_server_uses_slug_arg_in_shell_out(self):
captured = {}
def fake_run_result(cmd, timeout=None):
captured["cmd"] = cmd
return sfx.RunResult(True, json.dumps({"state": "ok"}), 0, "")
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(sfx, "run_result", side_effect=fake_run_result):
sfx._probe_server("metadata-experts")
self.assertIn("--probe", captured["cmd"])
self.assertEqual(captured["cmd"][-1], "metadata-experts")
self.assertNotIn("salesforce-metadata-experts", captured["cmd"])
def test_probe_server_nonzero_exit_renders_warn_not_crash(self):
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(sfx, "run_result",
return_value=sfx.RunResult(False, "", 1, "nonzero")):
row = sfx._probe_server("metadata-experts")
self.assertEqual(row["status"], "warn")
def test_probe_server_unparseable_stdout_renders_warn_not_crash(self):
with mock.patch.object(Path, "exists", return_value=True), \
mock.patch.object(sfx, "run_result",
return_value=sfx.RunResult(True, "not json", 0, "")):
row = sfx._probe_server("metadata-experts")
self.assertEqual(row["status"], "warn")
def test_probe_server_missing_proxy_bundle_renders_warn_not_crash(self):
with mock.patch.object(Path, "exists", return_value=False):
row = sfx._probe_server("metadata-experts")
self.assertEqual(row["status"], "warn")
# --- _passive_mcp_summary (WIN-033 /status banner) -------------------
# The banner summary is network-free (reads only the sidecars) and MUST
# surface the worst observed state so an inactive server is never hidden
# behind a healthy one.
def _fake_sidecars(self, by_slug, org=None):
"""Return a _read_health_sidecar stand-in keyed by slug arg. A value may
be a bare state string, or a (state, org) tuple to model the sidecar's
`org` field; `org=` sets a default org for bare-string entries."""
def _reader(slug):
entry = by_slug.get(slug)
if entry is None:
return None
if isinstance(entry, tuple):
state, entry_org = entry
else:
state, entry_org = entry, org
return {"slug": slug, "state": state, "org": entry_org}
return _reader
def test_summary_all_ok_reports_both_active(self):
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"})):
summary = sfx._passive_mcp_summary()
self.assertIn("active", summary.lower())
self.assertNotIn("not activated", summary.lower())
def test_summary_inactive_surfaces_not_activated_even_if_other_ok(self):
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "inactive"})):
summary = sfx._passive_mcp_summary()
self.assertIn("NOT activated", summary)
self.assertIn("metadata-experts", summary)
def test_summary_no_sidecars_is_not_yet_observed_not_invented(self):
with mock.patch.object(sfx, "_read_health_sidecar", return_value=None):
summary = sfx._passive_mcp_summary()
self.assertIn("not yet observed", summary.lower())
def test_summary_mixed_degraded_points_at_check_tools(self):
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "auth"})):
summary = sfx._passive_mcp_summary()
self.assertIn("degraded", summary.lower())
self.assertIn("check-tools", summary.lower())
# --- partial observation is PENDING, not an outage (review P1 #1) ---------
# One server ok, the other not yet observed (no sidecar), none bad: this is
# still connecting, so the summary must read as pending ("not yet observed"),
# never "degraded" — otherwise _mcp_indicator paints a false ✗ unavailable.
def test_summary_partial_ok_and_unobserved_is_pending_not_degraded(self):
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok"})): # metadata-experts absent
summary = sfx._passive_mcp_summary()
self.assertIn("not yet observed", summary.lower())
self.assertNotIn("degraded", summary.lower())
self.assertNotIn("active", summary.lower()) # not a full-green claim either
# And the banner icon derives to connecting, not unavailable.
icon, style = sfx._mcp_indicator(summary)
self.assertIn("connecting", icon)
self.assertNotIn("unavailable", icon)
# --- org-scoped observations (review P1 #2) -------------------------------
# A sidecar written against a DIFFERENT org must not be shown as healthy for
# the org the user is currently on.
def test_summary_ignores_sidecar_from_a_different_org(self):
# Both servers ok, but recorded against "orgA"; active org is "orgB".
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="orgA")):
summary = sfx._passive_mcp_summary(active_org="orgB")
# No usable observation for orgB -> neutral not-yet-observed, NOT active.
self.assertIn("not yet observed", summary.lower())
self.assertNotIn("active", summary.lower())
def test_summary_accepts_sidecar_matching_active_org(self):
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="orgA")):
summary = sfx._passive_mcp_summary(active_org="orgA")
self.assertIn("active", summary.lower())
def test_summary_no_active_org_does_not_filter(self):
# When the active org is unknown, fall back to state-only (no over-filter).
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="orgA")):
summary = sfx._passive_mcp_summary() # no active_org
self.assertIn("active", summary.lower())
def test_summary_accepts_sidecar_by_username_when_resolved_by_alias(self):
# review P2 #2: the producer stamps the configured USERNAME while the
# consumer resolves the SAME org by ALIAS. Passing both identifiers must
# accept the username-stamped sidecar (not reject it as a foreign org).
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"},
org="user@example.com")):
summary = sfx._passive_mcp_summary(
active_org=("myAlias", "user@example.com"))
self.assertIn("active", summary.lower())
self.assertNotIn("not yet observed", summary.lower())
def test_summary_still_rejects_truly_foreign_org_with_both_ids(self):
# The alias/username tolerance must not defeat the org filter: a sidecar
# from a genuinely different org is still rejected when neither the alias
# nor the username matches.
with mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="otherOrg")):
summary = sfx._passive_mcp_summary(
active_org=("myAlias", "user@example.com"))
self.assertIn("not yet observed", summary.lower())
self.assertNotIn("active", summary.lower())
# --- _live_mcp_summary (WIN-040 live-probe banner) ------------------------
# The live summary actively probes each server so the banner reflects REAL
# current reachability. A fresh probe is authoritative for this session's org
# and OVERRIDES a stale sidecar (the activate-then-still-inactive demo gap);
# a probe that cannot run falls back to that server's last-known sidecar.
def _fake_probes(self, by_slug):
"""Return a _probe_server_raw stand-in keyed by slug arg. A value may be a
bare state string (-> {slug, state, org}) or None (probe could not run)."""
def _probe(slug, timeout=None):
state = by_slug.get(slug)
if state is None:
return None
return {"slug": slug, "state": state, "org": "liveOrg"}
return _probe
def test_live_summary_probe_overrides_stale_inactive_sidecar(self):
# Sidecars say inactive (stale); live probe says ok -> summary is active.
with mock.patch.object(sfx, "_probe_server_raw",
side_effect=self._fake_probes(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"})), \
mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "inactive",
"metadata-experts": "inactive"})):
summary = sfx._live_mcp_summary(active_org="liveOrg")
self.assertIn("active", summary.lower())
self.assertNotIn("not activated", summary.lower())
def test_live_summary_probe_surfaces_inactive_over_stale_ok(self):
# The reverse: sidecar says ok (stale), live probe says inactive.
with mock.patch.object(sfx, "_probe_server_raw",
side_effect=self._fake_probes(
{"salesforce-api-context": "ok",
"metadata-experts": "inactive"})), \
mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"})):
summary = sfx._live_mcp_summary(active_org="liveOrg")
self.assertIn("NOT activated", summary)
self.assertIn("metadata-experts", summary)
def test_live_summary_falls_back_to_sidecar_when_probe_cannot_run(self):
# Both probes fail to run (None); the last-known org-filtered sidecars are
# used so a transient/offline failure degrades to the cached reading.
with mock.patch.object(sfx, "_probe_server_raw",
side_effect=self._fake_probes({})), \
mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="cachedOrg")):
summary = sfx._live_mcp_summary(active_org="cachedOrg")
self.assertIn("active", summary.lower())
def test_live_summary_probe_failure_plus_foreign_sidecar_is_pending(self):
# Probe can't run AND the only sidecar is from another org -> no usable
# observation -> neutral not-yet-observed, never a false green.
with mock.patch.object(sfx, "_probe_server_raw",
side_effect=self._fake_probes({})), \
mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"salesforce-api-context": "ok",
"metadata-experts": "ok"}, org="otherOrg")):
summary = sfx._live_mcp_summary(active_org="thisOrg")
self.assertIn("not yet observed", summary.lower())
self.assertNotIn("active", summary.lower())
# --- partial: one tracked server healthy, one down (user-requested glyph) --
# A half-working feature is neither a full outage nor healthy: the summary
# reads "partial", names the down server, and the banner glyph is ⚠ partial —
# distinct from both ✓ connected (all ok) and ✗ unavailable (all down).
def test_summary_one_ok_one_inactive_is_partial(self):
summary = sfx._summarize_mcp_states(
{"salesforce-api-context": "ok", "metadata-experts": "inactive"})
self.assertIn("partial", summary.lower())
self.assertIn("metadata-experts", summary) # names the down server
icon, style = sfx._mcp_indicator(summary)
self.assertIn("partial", icon)
self.assertNotIn("connected", icon) # not a false green
self.assertNotIn("unavailable", icon) # not a full outage either
def test_summary_one_ok_one_auth_is_partial(self):
summary = sfx._summarize_mcp_states(
{"salesforce-api-context": "ok", "metadata-experts": "auth"})
self.assertIn("partial", summary.lower())
self.assertIn("partial", sfx._mcp_indicator(summary)[0])
def test_summary_both_inactive_is_full_unavailable_not_partial(self):
summary = sfx._summarize_mcp_states(
{"salesforce-api-context": "inactive", "metadata-experts": "inactive"})
self.assertNotIn("partial", summary.lower())
self.assertIn("unavailable", sfx._mcp_indicator(summary)[0])
def test_partial_summary_icon_precedence_over_active_substring(self):
# The partial summary contains the word "active" ("others active"); the
# indicator MUST test "partial" first so it never paints a false ✓.
summary = ("sf-mcp-proxy: partial — metadata-experts NOT activated in this "
"org (others active) — enable in Setup (check-tools for detail)")
icon, _ = sfx._mcp_indicator(summary)
self.assertIn("partial", icon)
self.assertNotIn("connected", icon)
def test_live_summary_mixes_live_probe_with_sidecar_fallback(self):
# One server probes live (ok); the other's probe fails but its sidecar is
# a fresh inactive -> the inactive must still surface (worst-of).
def _one_probe(slug, timeout=None):
if slug == "salesforce-api-context":
return {"slug": slug, "state": "ok", "org": "liveOrg"}
return None # metadata-experts probe could not run
with mock.patch.object(sfx, "_probe_server_raw", side_effect=_one_probe), \
mock.patch.object(sfx, "_read_health_sidecar",
side_effect=self._fake_sidecars(
{"metadata-experts": "inactive"}, org="liveOrg")):
summary = sfx._live_mcp_summary(active_org="liveOrg")
self.assertIn("NOT activated", summary)
self.assertIn("metadata-experts", summary)
# --- banner icon derivation (WIN-033 /status org-box "MCP" field) -----
# render_banner_message() derives the compact ✓/⟳/✗ icon from the health
# summary string. It MUST understand the _passive_mcp_summary() vocabulary,
# not only the legacy "connected/connecting/bridged" strings — otherwise a
# healthy "... active" summary falls through to "✗ unavailable" and the icon
# contradicts the Note (regression caught in live dry-run against DEorgFRI).
def _banner_for(self, summary):
org = {"alias": "x", "edition": "e", "apiVersion": "62.0",
"instanceUrl": "u", "username": "n"}
proj = {"name": "P", "source_api": "62.0", "package_dirs": "force-app"}
stats = {k: 0 for k in ("apex_src", "apex_test", "triggers", "lwc",
"aura", "objects", "permsets", "flows")}
return sfx.render_banner_message(org, proj, stats, "", summary)
def test_banner_icon_active_summary_is_connected_no_note(self):
out = self._banner_for("sf-mcp-proxy: api-context, metadata-experts active")
self.assertIn("✓ connected", out)
self.assertNotIn("✗ unavailable", out)
self.assertNotIn("Note:", out)
def test_banner_icon_inactive_summary_is_unavailable(self):
summary = ("sf-mcp-proxy: metadata-experts NOT activated in this org — "
"enable in Setup (check-tools for detail)")
out = self._banner_for(summary)
# "active" is a substring of "NOT activated" — the icon must NOT be fooled.
self.assertIn("✗ unavailable", out)
self.assertNotIn("✓ connected", out)
# The environment band shows only the tri-state icon; per-server detail
# lives in check-tools (WIN-040), so there is no verbose Note line here.
def test_banner_icon_not_yet_observed_is_connecting_no_note(self):
out = self._banner_for("sf-mcp-proxy: not yet observed — run check-tools to probe")
self.assertIn("⟳ connecting", out)
self.assertNotIn("Note:", out)
def test_banner_icon_degraded_summary_is_unavailable(self):
out = self._banner_for("sf-mcp-proxy: degraded — run check-tools for per-server detail")
self.assertIn("✗ unavailable", out)
# --- MCP names line is scoped to the servers the glyph covers -------------
# The names shown next to the single ✓/✗ glyph must be ONLY the health-tracked
# platform servers. salesforce-lsp is a local stdio process the glyph never
# reflects, so listing it beside the glyph misleads the viewer.
def test_mcp_server_names_excludes_local_lsp(self):
mcp_json = json.dumps({"mcpServers": {
"salesforce-api-context": {},
"salesforce-lsp": {},
"salesforce-metadata-experts": {},
}})
with mock.patch.object(Path, "read_text", return_value=mcp_json):
names = sfx._mcp_server_names(Path("/plugin"))
self.assertIn("api-context", names)
self.assertIn("metadata-experts", names)
self.assertNotIn("lsp", names)
def test_mcp_server_names_read_error_yields_empty(self):
with mock.patch.object(Path, "read_text", side_effect=OSError("boom")):
self.assertEqual(sfx._mcp_server_names(Path("/plugin")), [])
# --- cmd_status must not probe an unreachable org -------------------------
# The live probe runs on an executor thread that cannot be cancelled, so
# cmd_status resolves the org FIRST and only probes when it is reachable.
# Probing before the unreachable-org early return would leave a live thread
# that concurrent.futures joins at interpreter exit, hanging /status until the
# probe subprocesses time out (Prizm P2 on 94bab3b).
def test_cmd_status_unreachable_org_does_not_probe(self):
with mock.patch.object(sfx.Path, "exists", return_value=True), \
mock.patch.object(sfx, "resolve_executable", return_value="/usr/bin/sf"), \
mock.patch.object(sfx, "get_target_org_detailed",
return_value=("deadOrg", "")), \
mock.patch.object(sfx, "resolve_org_info", return_value=None), \
mock.patch.object(sfx, "_live_mcp_summary",
side_effect=AssertionError(
"must not probe an unreachable org")) as probe, \
mock.patch("builtins.print"):
rc = sfx.cmd_status()
self.assertEqual(rc, 0)
probe.assert_not_called()
class DiagnosticTests(unittest.TestCase):
def test_diagnostic_shape(self):
ctx = sfx.diagnostic_context(["sf", "npm"])
self.assertEqual(ctx["platform"], sfx.sys.platform)
self.assertIn("sf", ctx["resolvedExecutables"])
self.assertIn("npm", ctx["resolvedExecutables"])
def test_diagnostic_is_secret_free(self):
# The diagnostic must never carry tokens/secrets — only environment shape
# and resolved executable paths.
ctx = sfx.diagnostic_context()
blob = json.dumps(ctx).lower()
for forbidden in ("token", "jwt", "secret", "password", "authorization", "bearer"):
self.assertNotIn(forbidden, blob)
def test_render_diagnostic_lines_is_text(self):
text = sfx.render_diagnostic_lines(sfx.diagnostic_context(["sf"]))
self.assertIn("platform:", text)
self.assertIn("resolved executables:", text)
if __name__ == "__main__":
unittest.main(verbosity=2)