Skip to content

Commit b3463b7

Browse files
committed
feat(tests): add 66+% cov tests
1 parent edcb134 commit b3463b7

File tree

9 files changed

+188
-32
lines changed

9 files changed

+188
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
</a>
1818
<img alt="ruff" src="https://img.shields.io/badge/Ruff-lint%2Fformat-9C27B0?style=for-the-badge&logo=ruff&logoColor=white">
1919
<img alt="python" src="https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white">
20+
<img alt="pytest coverage" src="https://img.shields.io/badge/Coverage-66%25-brightgreen?style=for-the-badge&logo=pytest">
2021
<img alt="license" src="https://img.shields.io/badge/License-MIT-success?style=for-the-badge">
2122

2223
</div>

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ dependencies = [
2626
"mcpstack>=0.0.2",
2727
]
2828

29-
[project.optional-dependencies]
29+
[dependency-groups]
3030
dev = [
3131
"pytest>=7.4",
3232
"ruff>=0.4.0",

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import sys
2+
import types
3+
import contextlib
4+
from typing import Iterable
5+
6+
import pytest
7+
8+
from mcpstack_jupyter.tools.jupyter.utils.config_loader import load_known_tools
9+
10+
11+
def _make_fn(name: str):
12+
def _fn(*args, **kwargs):
13+
return {"__tool_name__": name}
14+
_fn.__name__ = name
15+
return _fn
16+
17+
18+
@contextlib.contextmanager
19+
def install_fake_jupyter_server(tool_names: Iterable[str]):
20+
pkg_name = "jupyter_mcp_server"
21+
mod_name = "jupyter_mcp_server.server"
22+
23+
pkg = types.ModuleType(pkg_name)
24+
server = types.ModuleType(mod_name)
25+
26+
server.PROVIDER = None
27+
server.DOCUMENT_URL = None
28+
server.DOCUMENT_ID = None
29+
server.DOCUMENT_TOKEN = None
30+
server.RUNTIME_URL = None
31+
server.RUNTIME_ID = None
32+
server.RUNTIME_TOKEN = None
33+
34+
for n in tool_names:
35+
setattr(server, n, _make_fn(n))
36+
37+
prev_pkg = sys.modules.get(pkg_name)
38+
prev_mod = sys.modules.get(mod_name)
39+
sys.modules[pkg_name] = pkg
40+
sys.modules[mod_name] = server
41+
try:
42+
yield server
43+
finally:
44+
if prev_pkg is not None:
45+
sys.modules[pkg_name] = prev_pkg
46+
else:
47+
sys.modules.pop(pkg_name, None)
48+
if prev_mod is not None:
49+
sys.modules[mod_name] = prev_mod
50+
else:
51+
sys.modules.pop(mod_name, None)
52+
53+
54+
@pytest.fixture(scope="session")
55+
def known_tools():
56+
return load_known_tools()["known_tools"]

tests/test_config_loader.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from mcpstack_jupyter.tools.jupyter.utils.config_loader import (
2+
load_known_tools,
3+
load_env_defaults,
4+
load_cli_defaults,
5+
)
6+
7+
def test_known_tools_and_read_only_lists():
8+
data = load_known_tools()
9+
known = data["known_tools"]
10+
ro = data["read_only"]
11+
assert isinstance(known, list) and len(known) > 0
12+
assert "append_markdown_cell" in known
13+
assert "read_all_cells" in known
14+
assert set(ro).issubset(set(known))
15+
16+
def test_env_and_cli_defaults_present():
17+
env = load_env_defaults()
18+
cli = load_cli_defaults()
19+
assert "provider" in env
20+
assert "document_url" in env
21+
assert "output_filename" in cli

tests/test_jupyter_bindings.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import pytest
2+
3+
from mcpstack_jupyter.tools.jupyter.jupyter import Jupyter
4+
from mcpstack_jupyter.tools.jupyter.utils.config_loader import load_env_defaults
5+
from tests.conftest import install_fake_jupyter_server
6+
7+
8+
def action_names(bound_fns):
9+
return [fn.__name__ for fn in bound_fns]
10+
11+
12+
def test_bind_all_known_tools(known_tools):
13+
with install_fake_jupyter_server(known_tools) as server:
14+
tool = Jupyter()
15+
tool.initialize() # import & bind
16+
names = action_names(tool.actions())
17+
assert sorted(names) == sorted(known_tools)
18+
for n in known_tools:
19+
assert hasattr(server, n)
20+
21+
22+
def test_include_subset_only_binds_subset(known_tools):
23+
subset = known_tools[:3]
24+
with install_fake_jupyter_server(known_tools):
25+
tool = Jupyter(include=subset)
26+
tool.initialize()
27+
names = action_names(tool.actions())
28+
assert sorted(names) == sorted(subset)
29+
30+
31+
def test_missing_upstream_tool_raises_attribute_error(known_tools):
32+
missing = known_tools[-1]
33+
present = known_tools[:-1]
34+
with install_fake_jupyter_server(present):
35+
tool = Jupyter()
36+
with pytest.raises(AttributeError) as ei:
37+
tool.initialize()
38+
assert missing in str(ei.value)
39+
40+
41+
def test_env_mapping_defaults_and_overrides(monkeypatch, known_tools):
42+
defaults = load_env_defaults()
43+
with install_fake_jupyter_server(known_tools) as server:
44+
for k in ["PROVIDER","DOCUMENT_URL","DOCUMENT_ID","DOCUMENT_TOKEN",
45+
"RUNTIME_URL","RUNTIME_ID","RUNTIME_TOKEN"]:
46+
monkeypatch.delenv(k, raising=False)
47+
tool = Jupyter()
48+
tool.initialize()
49+
50+
assert server.PROVIDER == defaults.get("provider", "jupyter")
51+
assert server.DOCUMENT_URL == defaults.get("document_url", "http://127.0.0.1:8888")
52+
assert server.DOCUMENT_ID == defaults.get("document_id", "notebook.ipynb")
53+
assert server.RUNTIME_URL == defaults.get("runtime_url") or server.DOCUMENT_URL
54+
55+
with install_fake_jupyter_server(known_tools) as server:
56+
monkeypatch.setenv("PROVIDER", "custom")
57+
monkeypatch.setenv("DOCUMENT_URL", "http://10.0.0.1:9999")
58+
monkeypatch.setenv("DOCUMENT_ID", "foo.ipynb")
59+
monkeypatch.setenv("DOCUMENT_TOKEN", "DOC1234")
60+
monkeypatch.setenv("RUNTIME_URL", "http://10.0.0.2:8888")
61+
monkeypatch.setenv("RUNTIME_ID", "kernel-abc")
62+
monkeypatch.setenv("RUNTIME_TOKEN", "RUN5678")
63+
64+
tool = Jupyter()
65+
tool.initialize()
66+
67+
assert server.PROVIDER == "custom"
68+
assert server.DOCUMENT_URL == "http://10.0.0.1:9999"
69+
assert server.DOCUMENT_ID == "foo.ipynb"
70+
assert server.DOCUMENT_TOKEN == "DOC1234"
71+
assert server.RUNTIME_URL == "http://10.0.0.2:8888"
72+
assert server.RUNTIME_ID == "kernel-abc"
73+
assert server.RUNTIME_TOKEN == "RUN5678"
74+
75+
76+
def test_to_from_dict_roundtrip():
77+
src = {"include": ["read_all_cells", "get_notebook_info"]}
78+
tool = Jupyter.from_dict(src)
79+
assert tool.to_dict() == src

tests/test_status_cli.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import re
2+
3+
from mcpstack_jupyter.tools.jupyter.cli import JupyterCLI
4+
from tests.conftest import install_fake_jupyter_server
5+
6+
7+
def test_status_warns_when_tools_missing(known_tools, capsys, monkeypatch):
8+
present = known_tools[:-1]
9+
missing = known_tools[-1]
10+
with install_fake_jupyter_server(present):
11+
JupyterCLI.status(verbose=False)
12+
out = capsys.readouterr().out
13+
assert "Missing upstream tools" in out
14+
assert missing in out
15+
16+
17+
def test_status_verbose_lists_availability(known_tools, capsys):
18+
with install_fake_jupyter_server(known_tools):
19+
JupyterCLI.status(verbose=True)
20+
out = capsys.readouterr().out
21+
assert "Upstream Tool Availability" in out
22+
assert re.search(r"\byes\b", out, re.IGNORECASE)

tests/test_tool.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

uv.lock

Lines changed: 8 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)