mirror of
https://github.com/Ar9av/obsidian-wiki.git
synced 2026-09-14 20:36:34 +08:00
ea1633a8d4
* fix(tests): isolate CLI subprocess tests, and run the suite in CI
12 tests failed on a clean checkout. Neither was a product bug — both were
test-harness isolation gaps, and CI never caught them because the suite was
never run there.
1. Wrong package under test. The 18 test files that spawn
`python -m obsidian_wiki.cli` each build their own env from
os.environ.copy() and never set PYTHONPATH, so sys.path[0] was the
subprocess cwd. Any test running from a temp dir imported whatever
obsidian_wiki happened to be pip-installed instead of the working tree —
here a stale build predating `code-understand`, so all 8
test_code_understand_cli tests died at argparse with "invalid choice",
plus 4 in test_context_pack_cli. One autouse session fixture in
conftest.py now pins PYTHONPATH for every helper at once.
2. Developer .env leaking in. test_lint's `_run` and test_context_pack's
`run_cli` inherited pytest's cwd (the repo), so config resolution walked
up and found a developer's own untracked .env, pointing the run at their
real vault instead of the fixture. Both now run from the fake HOME.
Verified the fix isn't masking anything: sabotaging the config resolver makes
the relevant tests fail, and the suite passes in a venv with no obsidian_wiki
installed at all — so PYTHONPATH is genuinely doing the work.
Adds .github/workflows/tests.yml to run pytest on PRs and main. The workflows
only ran setup.sh, wheel publish, and the README-sync checker, so nothing
executed tests/ — the real reason this drifted.
Also drops the stale "RED by design / command does not exist yet" docstring
in test_code_understand_cli.py; the wiring landed in fd30720.
566 passed, 1 skipped (was 554 passed / 12 failed).
* fix(tests): make three test modules importable on Python 3.9
test_batch, test_graph_analysis and test_graphrag annotate helpers with
`int | None` / `list[str] | None` but lack `from __future__ import
annotations`, so 3.9 evaluates the union at import time and collection dies
with "unsupported operand type(s) for |". Every module under obsidian_wiki/
already has the future import; only these three tests were missing it.
Surfaced by the new pytest CI job — the 3.9 leg (the requires-python floor)
failed at collection while 3.12 passed.
190 lines
5.1 KiB
Python
190 lines
5.1 KiB
Python
"""Subprocess tests for the `code-understand` CLI command (issue #167).
|
|
|
|
These were written RED-first, before the subcommand existed; the wiring landed
|
|
in fd30720 and they are green now. They pin the exact CLI contract:
|
|
|
|
obsidian-wiki code-understand [--project <dir>] [--backend auto|builtin|codegraph]
|
|
[--changed <file> ...] [--since <sha>] [--max-symbols N] [--pretty]
|
|
|
|
Every invocation is a real subprocess (`python -m obsidian_wiki.cli`) via the
|
|
conftest `_run_cli` helper, so these tests lock the observable contract — exit
|
|
codes, JSON shape on stdout, and stderr messages — not any internal function.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from conftest import (
|
|
_run_cli,
|
|
make_fake_codegraph_bin,
|
|
make_project,
|
|
make_repo_two_commits,
|
|
)
|
|
|
|
# Minimal PATH so the code-understanding provider can never find a real
|
|
# codegraph on the machine running the tests. /usr/bin:/bin keeps `git` (and
|
|
# the absolute sys.executable used to launch the CLI) working while excluding
|
|
# any user/brew/.omo install of codegraph. `which codegraph` fails on the dev
|
|
# machine (vendored bin lives under ~/.omo but not on PATH), so this is belt
|
|
# and suspenders for CI machines that do have codegraph on PATH.
|
|
_NO_CODEGRAPH_PATH = "/usr/bin:/bin"
|
|
|
|
|
|
def _no_codegraph_env() -> dict[str, str]:
|
|
return {"PATH": _NO_CODEGRAPH_PATH, "CODE_UNDERSTANDING_CODEGRAPH_BIN": ""}
|
|
|
|
|
|
def _mini_project(tmp_path: Path) -> Path:
|
|
return make_project(tmp_path, {"src/foo.py": "class Foo:\n pass\n"})
|
|
|
|
|
|
def test_code_understand_builtin_json_shape(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project, "code-understand", "--backend", "builtin", "--project", str(project)
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
data = json.loads(proc.stdout)
|
|
assert data["backend"] == "builtin"
|
|
assert isinstance(data["focus_map"], list)
|
|
for item in data["focus_map"]:
|
|
assert "file" in item
|
|
assert "lines" in item
|
|
|
|
|
|
def test_code_understand_accepts_changed_files(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--backend",
|
|
"builtin",
|
|
"--project",
|
|
str(project),
|
|
"--changed",
|
|
"src/foo.py",
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
data = json.loads(proc.stdout)
|
|
assert "src/foo.py" in data["seed_files"]
|
|
|
|
|
|
def test_code_understand_since_delta(tmp_path: Path) -> None:
|
|
project, sha_first = make_repo_two_commits(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--backend",
|
|
"builtin",
|
|
"--project",
|
|
str(project),
|
|
"--since",
|
|
sha_first,
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
data = json.loads(proc.stdout)
|
|
# Only src/foo.py (modified) and src/bar.py (added) change after sha_first.
|
|
assert set(data["seed_files"]) == {"src/foo.py", "src/bar.py"}
|
|
|
|
|
|
def test_code_understand_explicit_codegraph_broken_returns_1(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--project",
|
|
str(project),
|
|
env_overrides={
|
|
"CODE_UNDERSTANDING_BACKEND": "codegraph",
|
|
**_no_codegraph_env(),
|
|
},
|
|
)
|
|
|
|
assert proc.returncode == 1
|
|
assert "codegraph" in proc.stderr.lower()
|
|
|
|
|
|
def test_code_understand_auto_falls_back_when_codegraph_absent(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--project",
|
|
str(project),
|
|
env_overrides={
|
|
"CODE_UNDERSTANDING_BACKEND": "auto",
|
|
**_no_codegraph_env(),
|
|
},
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
data = json.loads(proc.stdout)
|
|
assert data["backend"] == "builtin"
|
|
assert data["warnings"]
|
|
|
|
|
|
def test_code_understand_codegraph_via_bin_env(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
bin_path = make_fake_codegraph_bin(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--project",
|
|
str(project),
|
|
env_overrides={
|
|
**_no_codegraph_env(),
|
|
"CODE_UNDERSTANDING_BACKEND": "codegraph",
|
|
"CODE_UNDERSTANDING_CODEGRAPH_BIN": str(bin_path),
|
|
},
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
data = json.loads(proc.stdout)
|
|
assert data["backend"] == "codegraph"
|
|
|
|
|
|
def test_code_understand_bad_project_returns_1(tmp_path: Path) -> None:
|
|
proc = _run_cli(
|
|
tmp_path,
|
|
"code-understand",
|
|
"--backend",
|
|
"builtin",
|
|
"--project",
|
|
"/nonexistent/xyz",
|
|
)
|
|
|
|
assert proc.returncode == 1
|
|
assert "project" in proc.stderr.lower()
|
|
|
|
|
|
def test_code_understand_pretty_flag(tmp_path: Path) -> None:
|
|
project = _mini_project(tmp_path)
|
|
|
|
proc = _run_cli(
|
|
project,
|
|
"code-understand",
|
|
"--backend",
|
|
"builtin",
|
|
"--project",
|
|
str(project),
|
|
"--pretty",
|
|
)
|
|
|
|
assert proc.returncode == 0
|
|
with pytest.raises(json.JSONDecodeError):
|
|
json.loads(proc.stdout)
|
|
assert "backend" in proc.stdout
|