Skip to content

Commit d304615

Browse files
tbitcsoz-agent
andcommitted
fix: resolve all 12 CodeQL alerts in test_mcp_server.py
py/import-and-import-from (10 alerts): Added a single module-level 'import specsmith.mcp_server as mcp_mod' and removed all inline 'import specsmith.mcp_server as _mod' and 'from specsmith.mcp_server import X' inside test methods, replacing every reference with 'mcp_mod.X'. The file now uses one consistent import style for specsmith.mcp_server throughout. py/uninitialized-local-variable (2 alerts): Added 'return' after each pytest.skip() call in TestMcpServeCli subprocess tests so CodeQL's flow analysis sees the exception-exit path as explicitly terminating, preventing false-positive 'proc used before initialization' warnings at lines 592 and 629. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent d6c400a commit d304615

1 file changed

Lines changed: 52 additions & 105 deletions

File tree

tests/test_mcp_server.py

Lines changed: 52 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import pytest
2323

24+
import specsmith.mcp_server as mcp_mod
25+
2426

2527
# ---------------------------------------------------------------------------
2628
# Autouse: suppress auto-update noise
@@ -48,9 +50,7 @@ def _make_rpc(method: str, params: dict[str, Any] | None = None, req_id: int = 1
4850

4951
def _send_rpc(msg: dict[str, Any]) -> dict[str, Any]:
5052
"""Feed one JSON-RPC message to _handle_request and return its response."""
51-
from specsmith.mcp_server import _handle_request
52-
53-
resp = _handle_request(msg)
53+
resp = mcp_mod._handle_request(msg)
5454
assert resp is not None, "Expected a response but got None"
5555
return resp
5656

@@ -100,11 +100,9 @@ def test_each_tool_has_input_schema(self) -> None:
100100
assert tool["inputSchema"]["type"] == "object"
101101

102102
def test_notification_returns_none(self) -> None:
103-
from specsmith.mcp_server import _handle_request
104-
105103
# Notifications have no id — no response expected
106104
msg = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}
107-
assert _handle_request(msg) is None
105+
assert mcp_mod._handle_request(msg) is None
108106

109107
def test_unknown_method_returns_error(self) -> None:
110108
resp = _send_rpc(_make_rpc("no_such_method", {}, req_id=99))
@@ -126,10 +124,10 @@ def test_ping_returns_ok(self) -> None:
126124
assert resp["result"] == {}
127125

128126
def test_parse_error_on_bad_json(self) -> None:
129-
from specsmith.mcp_server import _handle_request
130-
131127
# _handle_request only processes dicts; parse errors are handled in run_server
132-
resp = _handle_request({"jsonrpc": "2.0", "method": "initialize", "id": 10, "params": {}})
128+
resp = mcp_mod._handle_request(
129+
{"jsonrpc": "2.0", "method": "initialize", "id": 10, "params": {}}
130+
)
133131
assert resp is not None
134132
assert "result" in resp
135133

@@ -141,15 +139,11 @@ def test_parse_error_on_bad_json(self) -> None:
141139

142140
class TestGovernanceAudit:
143141
def test_returns_healthy_key(self, tmp_path: Path) -> None:
144-
from specsmith.mcp_server import _handle_governance_audit
145-
146-
result = _handle_governance_audit({"project_dir": str(tmp_path)})
142+
result = mcp_mod._handle_governance_audit({"project_dir": str(tmp_path)})
147143
assert "healthy" in result
148144

149145
def test_returns_checks_list(self, tmp_path: Path) -> None:
150-
from specsmith.mcp_server import _handle_governance_audit
151-
152-
result = _handle_governance_audit({"project_dir": str(tmp_path)})
146+
result = mcp_mod._handle_governance_audit({"project_dir": str(tmp_path)})
153147
assert isinstance(result.get("checks"), list)
154148

155149
def test_via_tools_call(self, tmp_path: Path) -> None:
@@ -167,16 +161,12 @@ def test_via_tools_call(self, tmp_path: Path) -> None:
167161

168162
class TestGovernanceCheckpoint:
169163
def test_returns_anchor_key(self, tmp_path: Path) -> None:
170-
from specsmith.mcp_server import _handle_governance_checkpoint
171-
172-
result = _handle_governance_checkpoint({"project_dir": str(tmp_path)})
164+
result = mcp_mod._handle_governance_checkpoint({"project_dir": str(tmp_path)})
173165
assert "anchor" in result
174166
assert result["anchor"].startswith("SPECSMITH-ANCHOR-")
175167

176168
def test_returns_phase_and_health(self, tmp_path: Path) -> None:
177-
from specsmith.mcp_server import _handle_governance_checkpoint
178-
179-
result = _handle_governance_checkpoint({"project_dir": str(tmp_path)})
169+
result = mcp_mod._handle_governance_checkpoint({"project_dir": str(tmp_path)})
180170
assert "health" in result
181171
assert "phase" in result
182172
assert "req_count" in result
@@ -196,15 +186,11 @@ def test_via_tools_call(self, tmp_path: Path) -> None:
196186

197187
class TestGovernancePreflight:
198188
def test_empty_intent_returns_rejected(self, tmp_path: Path) -> None:
199-
from specsmith.mcp_server import _handle_governance_preflight
200-
201-
result = _handle_governance_preflight({"intent": "", "project_dir": str(tmp_path)})
189+
result = mcp_mod._handle_governance_preflight({"intent": "", "project_dir": str(tmp_path)})
202190
assert result["decision"] == "rejected"
203191

204192
def test_returns_decision_key(self, tmp_path: Path) -> None:
205-
from specsmith.mcp_server import _handle_governance_preflight
206-
207-
result = _handle_governance_preflight(
193+
result = mcp_mod._handle_governance_preflight(
208194
{
209195
"intent": "read governance health status",
210196
"project_dir": str(tmp_path),
@@ -233,9 +219,7 @@ def test_via_tools_call(self, tmp_path: Path) -> None:
233219

234220
class TestGovernancePhase:
235221
def test_returns_phase_key(self, tmp_path: Path) -> None:
236-
from specsmith.mcp_server import _handle_governance_phase
237-
238-
result = _handle_governance_phase({"project_dir": str(tmp_path)})
222+
result = mcp_mod._handle_governance_phase({"project_dir": str(tmp_path)})
239223
assert "phase" in result
240224

241225
def test_via_tools_call(self, tmp_path: Path) -> None:
@@ -253,9 +237,7 @@ def test_via_tools_call(self, tmp_path: Path) -> None:
253237

254238
class TestGovernanceReqList:
255239
def test_missing_requirements_json_returns_error(self, tmp_path: Path) -> None:
256-
from specsmith.mcp_server import _handle_governance_req_list
257-
258-
result = _handle_governance_req_list({"project_dir": str(tmp_path)})
240+
result = mcp_mod._handle_governance_req_list({"project_dir": str(tmp_path)})
259241
assert "error" in result
260242

261243
def test_with_requirements_json(self, tmp_path: Path) -> None:
@@ -270,9 +252,7 @@ def test_with_requirements_json(self, tmp_path: Path) -> None:
270252
json.dumps([{"id": "TEST-001", "covers": "REQ-001"}]), encoding="utf-8"
271253
)
272254

273-
from specsmith.mcp_server import _handle_governance_req_list
274-
275-
result = _handle_governance_req_list({"project_dir": str(tmp_path)})
255+
result = mcp_mod._handle_governance_req_list({"project_dir": str(tmp_path)})
276256
assert result["total"] == 2
277257
assert result["covered"] == 1
278258
req_ids = [r["id"] for r in result["reqs"]]
@@ -290,9 +270,7 @@ def test_status_filter(self, tmp_path: Path) -> None:
290270
]
291271
(spec_dir / "requirements.json").write_text(json.dumps(reqs), encoding="utf-8")
292272

293-
from specsmith.mcp_server import _handle_governance_req_list
294-
295-
result = _handle_governance_req_list(
273+
result = mcp_mod._handle_governance_req_list(
296274
{
297275
"project_dir": str(tmp_path),
298276
"status_filter": "planned",
@@ -318,68 +296,56 @@ class TestRegistryFunctions:
318296
def test_read_registry_missing_file_returns_empty( # noqa: E501
319297
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
320298
) -> None:
321-
import specsmith.mcp_server as _mod
322-
323299
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
324300
# No file created — should return empty list
325-
result = _mod.read_registry()
301+
result = mcp_mod.read_registry()
326302
assert result == []
327303

328304
def test_write_then_read_roundtrip(
329305
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
330306
) -> None:
331-
import specsmith.mcp_server as _mod
332-
333307
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
334308
paths = ["/path/to/proj1", "/path/to/proj2"]
335-
_mod.write_registry(paths)
336-
assert _mod.read_registry() == paths
309+
mcp_mod.write_registry(paths)
310+
assert mcp_mod.read_registry() == paths
337311

338312
def test_register_project_adds_new_entry(
339313
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
340314
) -> None:
341-
import specsmith.mcp_server as _mod
342-
343315
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
344316
proj = tmp_path / "myproject"
345317
proj.mkdir()
346-
added = _mod.register_project(str(proj))
318+
added = mcp_mod.register_project(str(proj))
347319
assert added is True
348-
assert str(proj) in _mod.read_registry()
320+
assert str(proj) in mcp_mod.read_registry()
349321

350322
def test_register_project_idempotent(
351323
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
352324
) -> None:
353-
import specsmith.mcp_server as _mod
354-
355325
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
356326
proj = tmp_path / "myproject"
357327
proj.mkdir()
358-
_mod.register_project(str(proj))
359-
added_again = _mod.register_project(str(proj))
328+
mcp_mod.register_project(str(proj))
329+
added_again = mcp_mod.register_project(str(proj))
360330
assert added_again is False
361-
assert _mod.read_registry().count(str(proj)) == 1
331+
assert mcp_mod.read_registry().count(str(proj)) == 1
362332

363333
def test_unregister_project_removes_entry(
364334
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
365335
) -> None:
366-
import specsmith.mcp_server as _mod
367-
368336
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
369337
proj = tmp_path / "myproject"
370338
proj.mkdir()
371-
_mod.register_project(str(proj))
372-
removed = _mod.unregister_project(str(proj))
339+
mcp_mod.register_project(str(proj))
340+
removed = mcp_mod.unregister_project(str(proj))
373341
assert removed is True
374-
assert str(proj) not in _mod.read_registry()
342+
assert str(proj) not in mcp_mod.read_registry()
375343

376344
def test_unregister_project_returns_false_if_not_registered(
377345
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
378346
) -> None:
379-
import specsmith.mcp_server as _mod
380-
381347
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
382-
result = _mod.unregister_project("/nonexistent/path")
348+
result = mcp_mod.unregister_project("/nonexistent/path")
383349
assert result is False
384350

385351
def test_registry_based_server_startup(
@@ -388,50 +354,40 @@ def test_registry_based_server_startup(
388354
"""run_server with no args should pick up projects from the registry."""
389355
import io
390356

391-
import specsmith.mcp_server as _mod
392-
from specsmith.mcp_server import run_server
393-
394357
monkeypatch.setenv("SPECSMITH_HOME", str(tmp_path))
395358
proj_a = tmp_path / "alpha"
396359
proj_a.mkdir()
397360
proj_b = tmp_path / "beta"
398361
proj_b.mkdir()
399-
_mod.register_project(str(proj_a))
400-
_mod.register_project(str(proj_b))
362+
mcp_mod.register_project(str(proj_a))
363+
mcp_mod.register_project(str(proj_b))
401364

402365
old_stdin = sys.stdin
403366
sys.stdin = io.StringIO("")
404367
try:
405-
run_server() # no project_dir arg — should use registry
368+
mcp_mod.run_server() # no project_dir arg — should use registry
406369
finally:
407370
sys.stdin = old_stdin
408371

409372
# First registered project is the default
410-
assert str(proj_a) == _mod._DEFAULT_PROJECT_DIR
411-
assert str(proj_b) in _mod._REGISTERED_PROJECTS
373+
assert str(proj_a) == mcp_mod._DEFAULT_PROJECT_DIR
374+
assert str(proj_b) in mcp_mod._REGISTERED_PROJECTS
412375

413376

414377
class TestGovernanceProjectList:
415378
def test_returns_default_project(self, tmp_path: Path) -> None:
416-
from specsmith.mcp_server import _handle_governance_project_list, run_server
417-
418379
# Prime server state with a known directory
419-
run_server.__func__ if hasattr(run_server, "__func__") else run_server # noqa: B018
420-
import specsmith.mcp_server as _mod
380+
mcp_mod._DEFAULT_PROJECT_DIR = str(tmp_path)
381+
mcp_mod._REGISTERED_PROJECTS = [str(tmp_path)]
421382

422-
_mod._DEFAULT_PROJECT_DIR = str(tmp_path)
423-
_mod._REGISTERED_PROJECTS = [str(tmp_path)]
424-
425-
result = _handle_governance_project_list({})
383+
result = mcp_mod._handle_governance_project_list({})
426384
assert result["default_project"] == str(tmp_path)
427385
assert str(tmp_path) in result["projects"]
428386
assert result["count"] >= 1
429387

430388
def test_via_tools_call(self, tmp_path: Path) -> None:
431-
import specsmith.mcp_server as _mod
432-
433-
_mod._DEFAULT_PROJECT_DIR = str(tmp_path)
434-
_mod._REGISTERED_PROJECTS = [str(tmp_path)]
389+
mcp_mod._DEFAULT_PROJECT_DIR = str(tmp_path)
390+
mcp_mod._REGISTERED_PROJECTS = [str(tmp_path)]
435391

436392
resp = _send_rpc(
437393
_make_rpc(
@@ -449,36 +405,31 @@ def test_extra_projects_registered(self, tmp_path: Path) -> None:
449405
"""run_server registers extra_project_dirs and they appear in project_list."""
450406
import io
451407

452-
import specsmith.mcp_server as _mod
453-
from specsmith.mcp_server import run_server
454-
455408
extra = tmp_path / "extra"
456409
extra.mkdir()
457410

458411
# Patch stdin to EOF immediately so run_server exits
459412
old_stdin = sys.stdin
460413
sys.stdin = io.StringIO("")
461414
try:
462-
run_server(project_dir=str(tmp_path), extra_project_dirs=[str(extra)])
415+
mcp_mod.run_server(project_dir=str(tmp_path), extra_project_dirs=[str(extra)])
463416
finally:
464417
sys.stdin = old_stdin
465418

466-
assert str(tmp_path.resolve()) == _mod._DEFAULT_PROJECT_DIR
467-
assert str(extra.resolve()) in _mod._REGISTERED_PROJECTS
468-
assert _mod._REGISTERED_PROJECTS[0] == str(tmp_path.resolve())
419+
assert str(tmp_path.resolve()) == mcp_mod._DEFAULT_PROJECT_DIR
420+
assert str(extra.resolve()) in mcp_mod._REGISTERED_PROJECTS
421+
assert mcp_mod._REGISTERED_PROJECTS[0] == str(tmp_path.resolve())
469422

470423
def test_no_chdir_called(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
471424
"""run_server must NOT call os.chdir — paths are resolved absolutely."""
472425
import io
473426

474-
from specsmith.mcp_server import run_server
475-
476427
chdir_calls: list[str] = []
477428
monkeypatch.setattr("os.chdir", lambda p: chdir_calls.append(str(p)))
478429
old_stdin = sys.stdin
479430
sys.stdin = io.StringIO("")
480431
try:
481-
run_server(project_dir=str(tmp_path))
432+
mcp_mod.run_server(project_dir=str(tmp_path))
482433
finally:
483434
sys.stdin = old_stdin
484435

@@ -487,9 +438,7 @@ def test_no_chdir_called(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch)
487438

488439
class TestGovernanceTraceSeal:
489440
def test_missing_description_returns_error(self, tmp_path: Path) -> None:
490-
from specsmith.mcp_server import _handle_governance_trace_seal
491-
492-
result = _handle_governance_trace_seal(
441+
result = mcp_mod._handle_governance_trace_seal(
493442
{
494443
"seal_type": "milestone",
495444
"description": "",
@@ -503,9 +452,7 @@ def test_seals_milestone(self, tmp_path: Path) -> None:
503452
spec_dir = tmp_path / ".specsmith"
504453
spec_dir.mkdir()
505454

506-
from specsmith.mcp_server import _handle_governance_trace_seal
507-
508-
result = _handle_governance_trace_seal(
455+
result = mcp_mod._handle_governance_trace_seal(
509456
{
510457
"seal_type": "milestone",
511458
"description": "test seal for pytest",
@@ -520,12 +467,10 @@ def test_second_seal_chains_to_first(self, tmp_path: Path) -> None:
520467
spec_dir = tmp_path / ".specsmith"
521468
spec_dir.mkdir()
522469

523-
from specsmith.mcp_server import _handle_governance_trace_seal
524-
525-
_handle_governance_trace_seal(
470+
mcp_mod._handle_governance_trace_seal(
526471
{"seal_type": "decision", "description": "first", "project_dir": str(tmp_path)}
527472
)
528-
result2 = _handle_governance_trace_seal(
473+
result2 = mcp_mod._handle_governance_trace_seal(
529474
{"seal_type": "milestone", "description": "second", "project_dir": str(tmp_path)}
530475
)
531476
assert result2["seal_id"] == "SEAL-0002"
@@ -586,8 +531,9 @@ def test_initialize_via_subprocess(self) -> None:
586531
timeout=15,
587532
env=env,
588533
)
589-
except subprocess.TimeoutExpired:
534+
except subprocess.TimeoutExpired: # pragma: no cover
590535
pytest.skip("mcp serve subprocess timed out (slow CI env)")
536+
return # never reached; satisfies flow analysis
591537

592538
assert proc.stdout, f"No output from mcp serve (stderr: {proc.stderr[:500]})"
593539
response = json.loads(proc.stdout.strip().splitlines()[0])
@@ -623,8 +569,9 @@ def test_tools_list_via_subprocess(self) -> None:
623569
timeout=15,
624570
env=env,
625571
)
626-
except subprocess.TimeoutExpired:
572+
except subprocess.TimeoutExpired: # pragma: no cover
627573
pytest.skip("mcp serve subprocess timed out (slow CI env)")
574+
return # never reached; satisfies flow analysis
628575

629576
lines = [ln for ln in proc.stdout.strip().splitlines() if ln.strip()]
630577
assert len(lines) >= 2, f"Expected ≥2 responses, got: {lines}"

0 commit comments

Comments
 (0)