"""Tests for the lenient JSON repair pipeline (repair_json_text + parse_pasted_json_verbose). Every case here is a shape people actually paste from MCP docs, blog posts, and chat windows.""" import json import pytest import bcc_core as c GOOD = {"command": "npx", "args": ["-y", "@x/brave"]} def assert_brave(servers): assert "brave" in servers assert servers["brave"]["command"] == "npx" # --------------------------------------------------------------------------- # # Valid JSON passes through untouched # --------------------------------------------------------------------------- # def test_strict_json_no_notes(): servers, notes = c.parse_pasted_json_verbose('{"brave": {"command": "npx", "args": []}}') assert_brave(servers) assert notes == [] # --------------------------------------------------------------------------- # # Markdown / prose wrappers # --------------------------------------------------------------------------- # def test_markdown_fence(): text = 'Add this to your config:\n```json\n{"brave": {"command": "npx"}}\n```\nThen restart.' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("fence" in n for n in notes) def test_fence_without_language_tag(): text = '```\n{"brave": {"command": "npx"}}\n```' servers, _ = c.parse_pasted_json_verbose(text) assert_brave(servers) def test_prose_around_bare_json(): text = 'Paste the following: {"brave": {"command": "npx"}} and restart Claude.' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("before" in n or "after" in n for n in notes) # --------------------------------------------------------------------------- # # Comments # --------------------------------------------------------------------------- # def test_line_comments(): text = '{\n // the search server\n "brave": {"command": "npx"}\n}' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("//" in n for n in notes) def test_block_comments(): text = '{ /* config */ "brave": {"command": "npx"} }' servers, _ = c.parse_pasted_json_verbose(text) assert_brave(servers) def test_url_with_double_slash_is_not_a_comment(): text = '{"remote": {"url": "https://mcp.example.com/sse"}}' servers, notes = c.parse_pasted_json_verbose(text) assert servers["remote"]["url"] == "https://mcp.example.com/sse" assert notes == [] # --------------------------------------------------------------------------- # # Commas # --------------------------------------------------------------------------- # def test_trailing_comma_object(): text = '{"brave": {"command": "npx", "args": ["-y"],},}' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("trailing" in n for n in notes) def test_missing_comma_between_servers(): text = '{"brave": {"command": "npx"}\n"git": {"command": "uvx"}}' servers, notes = c.parse_pasted_json_verbose(text) assert "brave" in servers and "git" in servers assert any("missing comma" in n for n in notes) def test_missing_comma_between_string_fields(): text = '{"brave": {"command": "npx"\n"args": ["-y"]}}' servers, _ = c.parse_pasted_json_verbose(text) assert servers["brave"]["args"] == ["-y"] # --------------------------------------------------------------------------- # # Quotes # --------------------------------------------------------------------------- # def test_smart_quotes(): text = "{“brave”: {“command”: “npx”}}" servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("smart quotes" in n for n in notes) def test_single_quotes(): text = "{'brave': {'command': 'npx', 'args': ['-y']}}" servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("single-quoted" in n for n in notes) def test_single_quotes_with_inner_double(): text = """{'brave': {'command': 'npx', 'env': {'NOTE': 'say "hi"'}}}""" servers, _ = c.parse_pasted_json_verbose(text) assert servers["brave"]["env"]["NOTE"] == 'say "hi"' # --------------------------------------------------------------------------- # # JS-style laxness # --------------------------------------------------------------------------- # def test_unquoted_keys(): text = '{brave: {command: "npx", args: ["-y"]}}' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("bare object keys" in n for n in notes) def test_python_literals(): text = '{"brave": {"command": "npx", "enabled": True, "extra": None}}' servers, notes = c.parse_pasted_json_verbose(text) assert servers["brave"]["enabled"] is True assert servers["brave"]["extra"] is None assert any("literals" in n for n in notes) # --------------------------------------------------------------------------- # # Structure damage # --------------------------------------------------------------------------- # def test_braceless_fragment(): text = '"brave": {"command": "npx", "args": ["-y"]}' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("wrapped" in n for n in notes) def test_missing_closing_braces(): text = '{"mcpServers": {"brave": {"command": "npx", "args": ["-y"]' servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert any("unclosed" in n for n in notes) def test_kitchen_sink(): # fence + comment + single quotes + unquoted key + trailing comma + prose text = ( "Here's the config you need:\n" "```json\n" "{\n" " // brave search\n" " brave: {\n" " 'command': 'npx',\n" " 'args': ['-y', '@x/brave',],\n" " },\n" "}\n" "```\n" ) servers, notes = c.parse_pasted_json_verbose(text) assert_brave(servers) assert servers["brave"]["args"] == ["-y", "@x/brave"] assert len(notes) >= 4 # --------------------------------------------------------------------------- # # Suspicious-args detection (several argv tokens typed on one line) # --------------------------------------------------------------------------- # def test_split_flag_and_value_on_one_line(): fixed, notes = c.split_suspicious_args(["--directory /path/to/server", "run", "main.py"]) assert fixed == ["--directory", "/path/to/server", "run", "main.py"] assert len(notes) == 1 def test_split_full_option_string(): fixed, notes = c.split_suspicious_args(["-y @pkg/server --port 8080"]) assert fixed == ["-y", "@pkg/server", "--port", "8080"] assert notes def test_split_respects_quotes(): fixed, _ = c.split_suspicious_args(['--name "My Server"']) assert fixed == ["--name", "My Server"] def test_split_leaves_legit_spaces_alone(): args = ["/Users/me/My Documents", "hello world", "run"] fixed, notes = c.split_suspicious_args(args) assert fixed == args assert notes == [] def test_split_leaves_clean_args_alone(): args = ["--directory", "/path/to/server", "run", "main.py"] fixed, notes = c.split_suspicious_args(args) assert fixed == args assert notes == [] # --------------------------------------------------------------------------- # # Config-file repair (load-time) # --------------------------------------------------------------------------- # def test_repair_config_file_fixes_and_reports(tmp_path): p = tmp_path / "claude_desktop_config.json" p.write_text( '{\n "globalShortcut": "Cmd+Space",\n' " // my servers\n" ' "mcpServers": {\n' ' "brave": {"command": "npx", "args": ["-y"],},\n' " },\n}" ) cfg, notes, preview = c.repair_config_file(p) assert cfg["mcpServers"]["brave"]["command"] == "npx" assert cfg["globalShortcut"] == "Cmd+Space" # unrelated keys survive assert any("comment" in n for n in notes) assert any("trailing" in n for n in notes) assert json.loads(preview) == cfg # nothing was written to disk assert "//" in p.read_text() def test_repair_config_file_hopeless_raises(tmp_path): p = tmp_path / "claude_desktop_config.json" p.write_text("total nonsense, no braces at all") with pytest.raises(ValueError): c.repair_config_file(p) # --------------------------------------------------------------------------- # # Still fails cleanly on hopeless input # --------------------------------------------------------------------------- # def test_hopeless_input_raises_value_error(): with pytest.raises(ValueError): c.parse_pasted_json_verbose("this is just prose with no json at all") def test_junk_object_still_rejected(): with pytest.raises(ValueError): c.parse_pasted_json_verbose('{"junk": 5}') def test_empty_raises(): with pytest.raises(ValueError): c.parse_pasted_json_verbose(" ")