From 78595be2a27cae66c78d522afe3893f31c5b0078 Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 1 Jul 2026 23:43:30 -0400 Subject: [PATCH] test: migrate test_core.py script to a proper pytest suite under tests/ --- test_core.py | 92 ------------------------- tests/test_core.py | 167 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 92 deletions(-) delete mode 100644 test_core.py create mode 100644 tests/test_core.py diff --git a/test_core.py b/test_core.py deleted file mode 100644 index e7d5bec..0000000 --- a/test_core.py +++ /dev/null @@ -1,92 +0,0 @@ -import json, tempfile, shutil -from pathlib import Path -import bcc_core as c - -tmp = Path(tempfile.mkdtemp()) -ok = [] - -def check(name, cond): - ok.append(cond) - print(("PASS" if cond else "FAIL"), "-", name) - -# ---- 1. discovery (monkeypatch the base dir) ---- -base = tmp / "AppSupport" -for d in ("Claude", "Claude-Work", "NotClaude"): - (base / d).mkdir(parents=True) -(base / "Claude" / c.CONFIG_FILENAME).write_text("{}") -# Claude-Work has no config yet; NotClaude shouldn't match -c.app_support_base = lambda: base -profs = c.discover_profiles() -labels = sorted(p.label for p in profs) -check("discovers Claude + Claude-Work, not NotClaude", - "Claude" in labels and "Claude-Work" in labels and "NotClaude" not in labels) -check("flags missing config file", any(p.label=="Claude-Work" and not p.config_exists for p in profs)) - -# ---- 2. write preserves OTHER keys + order, only touches mcpServers ---- -cfgpath = tmp / "real.json" -original = { - "globalShortcut": "Cmd+Shift+Space", - "mcpServers": {"old": {"command": "node", "args": ["x.js"]}}, - "someOtherTool": {"keep": True}, -} -cfgpath.write_text(json.dumps(original, indent=2)) -cfg = c.load_config(cfgpath) -servers = c.extract_servers(cfg) -# add a new one, disable the old one -servers.append(c.ServerEntry("brave", {"command": "npx", "args": ["-y", "@x/brave"]}, True)) -servers[0].enabled = False -c.apply_servers(cfg, servers) -c.write_config(cfgpath, cfg) -written = json.loads(cfgpath.read_text()) -keys = list(written.keys()) -check("preserved globalShortcut", written.get("globalShortcut") == "Cmd+Shift+Space") -check("preserved someOtherTool", written.get("someOtherTool") == {"keep": True}) -check("disabled server parked in _disabledMcpServers", "old" in written.get("_disabledMcpServers", {})) -check("enabled server in mcpServers", "brave" in written.get("mcpServers", {})) -check("old not in active mcpServers", "old" not in written.get("mcpServers", {})) -check("key order kept (globalShortcut before mcpServers)", keys.index("globalShortcut") < keys.index("mcpServers")) -check("backup created", (cfgpath.parent / c.BACKUP_DIRNAME).is_dir() and any((cfgpath.parent / c.BACKUP_DIRNAME).iterdir())) - -# ---- 3. paste parser, all shapes ---- -full = '{"mcpServers": {"a": {"command": "node", "args": ["a.js"]}}}' -check("parse full config shape", list(c.parse_pasted_json(full)) == ["a"]) -inner = '{"b": {"command": "uvx", "args": ["mcp-server-git"]}}' -check("parse inner block shape", list(c.parse_pasted_json(inner)) == ["b"]) -bare = '{"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]}' -parsed = c.parse_pasted_json(bare) -check("parse bare server + suggests name 'filesystem'", "filesystem" in parsed) -remote = '{"url": "https://mcp.asana.com/sse"}' -check("parse remote, suggests host name", "mcp" in c.parse_pasted_json(remote)) -try: - c.parse_pasted_json('{"junk": 5}') - check("rejects junk", False) -except ValueError: - check("rejects junk", True) - -# ---- 4. validation ---- -bad = [c.ServerEntry("", {"command": "x"}, True), - c.ServerEntry("dup", {"command": "x"}, True), - c.ServerEntry("dup", {"command": "x"}, True), - c.ServerEntry("r", {"url": ""}, True), - c.ServerEntry("nocmd", {"command": ""}, True)] -probs = c.validate_servers(bad) -check("validation catches empty name", any("empty name" in p for p in probs)) -check("validation catches duplicate", any("Duplicate" in p for p in probs)) -check("validation catches missing url", any("no URL" in p for p in probs)) -check("validation catches missing command", any("no command" in p for p in probs)) -check("valid set passes clean", c.validate_servers([c.ServerEntry("good", {"command":"node"}, True)]) == []) - -# ---- 5. dependency check (python3 exists; bogusxyz does not) ---- -r_ok = c.check_dependency({"command": "python3"}) -check("dep check finds python3", r_ok["status"] == "ok") -r_missing = c.check_dependency({"command": "definitely-not-real-binary-xyz"}) -check("dep check flags missing", r_missing["status"] == "missing") -r_remote = c.check_dependency({"url": "https://example.com/mcp"}) -check("dep check marks remote", r_remote["status"] == "remote") -# windows-style wrapper: cmd /c npx ... -> should look past cmd to npx -r_wrap = c.check_dependency({"command": "cmd", "args": ["/c", "definitely-not-real-xyz", "foo"]}) -check("dep check sees through shell wrapper", "definitely-not-real-xyz" in r_wrap["label"]) - -print() -print(f"{sum(ok)}/{len(ok)} passed") -shutil.rmtree(tmp) diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..1a4b5a8 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,167 @@ +"""Pytest port of the original test_core.py script (same 23 behaviours, now +proper test functions with tmp_path/monkeypatch fixtures).""" + +import json + +import pytest + +import bcc_core as c + + +# --------------------------------------------------------------------------- # +# 1. Discovery +# --------------------------------------------------------------------------- # +@pytest.fixture +def fake_base(tmp_path, monkeypatch): + base = tmp_path / "AppSupport" + for d in ("Claude", "Claude-Work", "NotClaude"): + (base / d).mkdir(parents=True) + (base / "Claude" / c.CONFIG_FILENAME).write_text("{}") + monkeypatch.setattr(c, "app_support_base", lambda: base) + return base + + +def test_discovers_claude_dirs_not_others(fake_base): + labels = sorted(p.label for p in c.discover_profiles()) + assert "Claude" in labels + assert "Claude-Work" in labels + assert "NotClaude" not in labels + + +def test_flags_missing_config_file(fake_base): + profs = c.discover_profiles() + assert any(p.label == "Claude-Work" and not p.config_exists for p in profs) + + +# --------------------------------------------------------------------------- # +# 2. Write pipeline: preserves other keys + order, only touches mcpServers +# --------------------------------------------------------------------------- # +@pytest.fixture +def written_config(tmp_path): + cfgpath = tmp_path / "real.json" + original = { + "globalShortcut": "Cmd+Shift+Space", + "mcpServers": {"old": {"command": "node", "args": ["x.js"]}}, + "someOtherTool": {"keep": True}, + } + cfgpath.write_text(json.dumps(original, indent=2)) + cfg = c.load_config(cfgpath) + servers = c.extract_servers(cfg) + servers.append(c.ServerEntry("brave", {"command": "npx", "args": ["-y", "@x/brave"]}, True)) + servers[0].enabled = False + c.apply_servers(cfg, servers) + c.write_config(cfgpath, cfg) + return cfgpath, json.loads(cfgpath.read_text()) + + +def test_preserves_unrelated_keys(written_config): + _, written = written_config + assert written.get("globalShortcut") == "Cmd+Shift+Space" + assert written.get("someOtherTool") == {"keep": True} + + +def test_disabled_server_parked(written_config): + _, written = written_config + assert "old" in written.get(c.DISABLED_KEY, {}) + assert "old" not in written.get("mcpServers", {}) + + +def test_enabled_server_active(written_config): + _, written = written_config + assert "brave" in written.get("mcpServers", {}) + + +def test_key_order_kept(written_config): + _, written = written_config + keys = list(written.keys()) + assert keys.index("globalShortcut") < keys.index("mcpServers") + + +def test_backup_created(written_config): + cfgpath, _ = written_config + bdir = cfgpath.parent / c.BACKUP_DIRNAME + assert bdir.is_dir() + assert any(bdir.iterdir()) + + +# --------------------------------------------------------------------------- # +# 3. Paste parser — all accepted shapes +# --------------------------------------------------------------------------- # +def test_parse_full_config_shape(): + full = '{"mcpServers": {"a": {"command": "node", "args": ["a.js"]}}}' + assert list(c.parse_pasted_json(full)) == ["a"] + + +def test_parse_inner_block_shape(): + inner = '{"b": {"command": "uvx", "args": ["mcp-server-git"]}}' + assert list(c.parse_pasted_json(inner)) == ["b"] + + +def test_parse_bare_server_suggests_name(): + bare = '{"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]}' + assert "filesystem" in c.parse_pasted_json(bare) + + +def test_parse_remote_suggests_host_name(): + remote = '{"url": "https://mcp.asana.com/sse"}' + assert "mcp" in c.parse_pasted_json(remote) + + +def test_rejects_junk(): + with pytest.raises(ValueError): + c.parse_pasted_json('{"junk": 5}') + + +# --------------------------------------------------------------------------- # +# 4. Validation +# --------------------------------------------------------------------------- # +@pytest.fixture +def problems(): + bad = [ + c.ServerEntry("", {"command": "x"}, True), + c.ServerEntry("dup", {"command": "x"}, True), + c.ServerEntry("dup", {"command": "x"}, True), + c.ServerEntry("r", {"url": ""}, True), + c.ServerEntry("nocmd", {"command": ""}, True), + ] + return c.validate_servers(bad) + + +def test_validation_catches_empty_name(problems): + assert any("empty name" in p for p in problems) + + +def test_validation_catches_duplicate(problems): + assert any("Duplicate" in p for p in problems) + + +def test_validation_catches_missing_url(problems): + assert any("no URL" in p for p in problems) + + +def test_validation_catches_missing_command(problems): + assert any("no command" in p for p in problems) + + +def test_valid_set_passes_clean(): + assert c.validate_servers([c.ServerEntry("good", {"command": "node"}, True)]) == [] + + +# --------------------------------------------------------------------------- # +# 5. Dependency / PATH checking +# --------------------------------------------------------------------------- # +def test_dep_check_finds_python3(): + assert c.check_dependency({"command": "python3"})["status"] == "ok" + + +def test_dep_check_flags_missing(): + assert c.check_dependency({"command": "definitely-not-real-binary-xyz"})["status"] == "missing" + + +def test_dep_check_marks_remote(): + assert c.check_dependency({"url": "https://example.com/mcp"})["status"] == "remote" + + +def test_dep_check_sees_through_shell_wrapper(): + r = c.check_dependency({"command": "cmd", "args": ["/c", "definitely-not-real-xyz", "foo"]}) + assert "definitely-not-real-xyz" in r["label"]