139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from docforge.changesets import ChangesetStore
|
|
from docforge.cli import _parser, _run
|
|
from docforge.project import Project
|
|
from docforge.viewer_manager import ViewerManager
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURES = ROOT / "tests" / "fixtures"
|
|
|
|
|
|
class DocForgeCliTests(unittest.TestCase):
|
|
def copy_fixture(self, destination: Path) -> Path:
|
|
root = destination / "alpha"
|
|
shutil.copytree(FIXTURES / "alpha", root)
|
|
return root
|
|
|
|
def test_traversal_commands_accept_explicit_result_limits(self) -> None:
|
|
parser = _parser()
|
|
for command in ("backlinks", "dependencies", "impact"):
|
|
with self.subTest(command=command):
|
|
arguments = parser.parse_args(
|
|
[
|
|
"--project-root",
|
|
"/tmp/project",
|
|
command,
|
|
"guide.workflow",
|
|
"--limit",
|
|
"7",
|
|
]
|
|
)
|
|
self.assertEqual(7, arguments.limit)
|
|
context = parser.parse_args(
|
|
[
|
|
"--project-root",
|
|
"/tmp/project",
|
|
"context",
|
|
"active",
|
|
"--limit",
|
|
"7",
|
|
"--cursor",
|
|
"opaque",
|
|
]
|
|
)
|
|
self.assertEqual(7, context.limit)
|
|
self.assertEqual("opaque", context.cursor)
|
|
|
|
def test_reindex_apply_and_visualization_commands_are_self_service(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
parent = Path(directory)
|
|
root = self.copy_fixture(parent)
|
|
parser = _parser()
|
|
reindexed = _run(parser.parse_args(["--project-root", str(root), "reindex"]))
|
|
self.assertTrue(reindexed["reindexed"])
|
|
synchronized = _run(parser.parse_args(["--project-root", str(root), "sync"]))
|
|
self.assertEqual("current", synchronized["synchronization"]["action"])
|
|
|
|
project = Project.open(root)
|
|
store = ChangesetStore(project, "alpha-editor")
|
|
node = next(node for node in project.load().nodes if node.node_id == "guide.workflow")
|
|
created = store.create("cli-apply")
|
|
proposed = store.propose_update(
|
|
changeset_id="cli-apply",
|
|
expected_changeset_hash=str(created["changeset_hash"]),
|
|
node_id=node.node_id,
|
|
expected_content_hash=node.content_hash,
|
|
metadata={"summary": "Applied through the CLI."},
|
|
content=None,
|
|
relationship_changes=[],
|
|
rationale="Verify the direct application command.",
|
|
)
|
|
applied = _run(
|
|
parser.parse_args(
|
|
[
|
|
"--project-root",
|
|
str(root),
|
|
"apply",
|
|
"cli-apply",
|
|
"--changeset-hash",
|
|
str(proposed["changeset_hash"]),
|
|
"--applier",
|
|
"alpha-editor",
|
|
]
|
|
)
|
|
)
|
|
self.assertTrue(applied["applied"])
|
|
|
|
state_path = parent / "viewer-manager.json"
|
|
manager = ViewerManager(state_path, check_interval_seconds=0.02)
|
|
thread = threading.Thread(target=manager.serve_forever, daemon=True)
|
|
previous = os.environ.get("DOCFORGE_VIEWER_MANAGER_STATE")
|
|
os.environ["DOCFORGE_VIEWER_MANAGER_STATE"] = str(state_path)
|
|
thread.start()
|
|
deadline = time.monotonic() + 2
|
|
while not state_path.exists() and time.monotonic() < deadline:
|
|
time.sleep(0.01)
|
|
try:
|
|
with mock.patch("docforge.cli.webbrowser.open", return_value=True) as opened:
|
|
visualized = _run(
|
|
parser.parse_args(
|
|
[
|
|
"--project-root",
|
|
str(root),
|
|
"visualize",
|
|
"--node",
|
|
"guide.workflow",
|
|
]
|
|
)
|
|
)
|
|
self.assertTrue(visualized["opened_browser"])
|
|
opened.assert_called_once()
|
|
status = _run(
|
|
parser.parse_args(["--project-root", str(root), "visualization-status"])
|
|
)
|
|
self.assertEqual("running", status["state"])
|
|
stopped = _run(
|
|
parser.parse_args(["--project-root", str(root), "visualization-stop"])
|
|
)
|
|
self.assertEqual("stopped", stopped["state"])
|
|
finally:
|
|
manager.shutdown()
|
|
thread.join(timeout=2)
|
|
if previous is None:
|
|
os.environ.pop("DOCFORGE_VIEWER_MANAGER_STATE", None)
|
|
else:
|
|
os.environ["DOCFORGE_VIEWER_MANAGER_STATE"] = previous
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|