90e5bbb99c
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 17s
CI / lint (pull_request) Successful in 3m17s
CI / unit_tests (pull_request) Successful in 3m38s
CI / quality (pull_request) Successful in 3m42s
CI / typecheck (pull_request) Successful in 3m54s
CI / security (pull_request) Successful in 4m2s
CI / docker (pull_request) Successful in 50s
CI / e2e_tests (pull_request) Successful in 8m32s
CI / integration_tests (pull_request) Successful in 6m54s
CI / coverage (pull_request) Successful in 14m10s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 59m15s
Extend the ResourceHandler protocol with six content operations (read, write, delete, list_children, diff, discover_children) and four frozen dataclass result types (Content, WriteResult, DeleteResult, DiffResult). Handler implementations: - GitCheckoutHandler: read via git show (binary-safe), write/delete via filesystem ops, list via git ls-tree, diff via git diff --no-index with locale-safe shortstat parsing, discover via git ls-tree -d - FsDirectoryHandler: full CRUD via pathlib/os/difflib/shutil - DevcontainerHandler: read/write/discover via devcontainer exec - CloudResourceHandler: NotImplementedError stubs for protocol compliance - DatabaseResourceHandler: inherits base NotImplementedError stubs Security: - Path traversal guard (_safe_resolve) on all read/write/delete ops using os.sep-suffixed startswith check to prevent prefix collisions - Empty-path deletion rejected with PermissionError Tests: - 22 Behave scenarios (115 steps): CRUD for FsDirectory and GitCheckout, path traversal rejection (3 scenarios), NotImplementedError defaults - 2 Robot integration tests: read -> write -> diff cycle on real temp directories and git repos ISSUES CLOSED: #827
182 lines
5.9 KiB
Python
182 lines
5.9 KiB
Python
"""Helper script for resource_handler_crud.robot integration tests.
|
|
|
|
Exercises the read -> write -> diff cycle on a real temporary git
|
|
repository using GitCheckoutHandler, and on a real temporary directory
|
|
using FsDirectoryHandler.
|
|
|
|
Issue #827: ResourceHandler CRUD and discovery methods.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
from typing import NoReturn
|
|
|
|
_ROOT = Path(__file__).resolve().parents[1]
|
|
_SRC = str(_ROOT / "src")
|
|
# Ensure local source takes precedence over installed package.
|
|
# Remove any existing cleveragents modules so importlib re-resolves
|
|
# from sys.path[0] (our local src).
|
|
sys.path.insert(0, _SRC)
|
|
for _mod_name in list(sys.modules):
|
|
if _mod_name.startswith("cleveragents"):
|
|
del sys.modules[_mod_name]
|
|
|
|
from cleveragents.domain.models.core.resource import ( # noqa: E402
|
|
PhysVirt,
|
|
Resource,
|
|
ResourceCapabilities,
|
|
)
|
|
from cleveragents.resource.handlers.fs_directory import FsDirectoryHandler # noqa: E402
|
|
from cleveragents.resource.handlers.git_checkout import GitCheckoutHandler # noqa: E402
|
|
|
|
|
|
def _fail(msg: str) -> NoReturn:
|
|
print(msg, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def _make_resource(rtype: str, location: str) -> Resource:
|
|
return Resource(
|
|
resource_id="01KJ5C5TPMP8GGX3QC83E2MAQS",
|
|
resource_type_name=rtype,
|
|
classification=PhysVirt.PHYSICAL,
|
|
location=location,
|
|
capabilities=ResourceCapabilities(
|
|
readable=True, writable=True, sandboxable=True
|
|
),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# fs-read-write-diff: FsDirectoryHandler read -> write -> diff cycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def fs_read_write_diff() -> None:
|
|
"""Test read -> write -> diff cycle on FsDirectoryHandler."""
|
|
handler = FsDirectoryHandler()
|
|
|
|
# Create two temp dirs: source and comparison
|
|
src = tempfile.mkdtemp(prefix="crud_robot_src_")
|
|
cmp_dir = tempfile.mkdtemp(prefix="crud_robot_cmp_")
|
|
|
|
# Write identical files to both
|
|
Path(src, "file.txt").write_text("original\n", encoding="utf-8")
|
|
Path(cmp_dir, "file.txt").write_text("original\n", encoding="utf-8")
|
|
|
|
resource = _make_resource("fs-directory", src)
|
|
|
|
# 1. Read
|
|
content = handler.read(resource=resource, path="file.txt")
|
|
if content.text.strip() != "original":
|
|
_fail(f"read failed: expected 'original', got '{content.text.strip()}'")
|
|
|
|
# 2. Write (modify)
|
|
result = handler.write(resource=resource, path="file.txt", data=b"modified\n")
|
|
if not result.success:
|
|
_fail(f"write failed: {result.message}")
|
|
|
|
# 3. Diff against comparison dir (should detect change)
|
|
diff = handler.diff(resource=resource, other_location=cmp_dir)
|
|
if not diff.has_changes:
|
|
_fail("diff should detect changes after write")
|
|
if diff.files_changed < 1:
|
|
_fail(f"diff files_changed={diff.files_changed}, expected >= 1")
|
|
|
|
# 4. List children
|
|
children = handler.list_children(resource=resource)
|
|
if "file.txt" not in children:
|
|
_fail(f"list_children missing 'file.txt': {children}")
|
|
|
|
print("fs-read-write-diff-ok")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# git-read-write-diff: GitCheckoutHandler read -> write -> diff cycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def git_read_write_diff() -> None:
|
|
"""Test read -> write -> diff cycle on GitCheckoutHandler."""
|
|
handler = GitCheckoutHandler()
|
|
|
|
# Create a temp git repo
|
|
repo = tempfile.mkdtemp(prefix="crud_robot_git_")
|
|
subprocess.run(["git", "init"], cwd=repo, capture_output=True, check=True)
|
|
subprocess.run(
|
|
["git", "config", "user.email", "test@test.com"],
|
|
cwd=repo,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
subprocess.run(
|
|
["git", "config", "user.name", "Test"],
|
|
cwd=repo,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
Path(repo, "code.py").write_text("print('hello')\n", encoding="utf-8")
|
|
subprocess.run(["git", "add", "."], cwd=repo, capture_output=True, check=True)
|
|
subprocess.run(
|
|
["git", "commit", "-m", "init"],
|
|
cwd=repo,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
|
|
resource = _make_resource("git-checkout", repo)
|
|
|
|
# Comparison dir
|
|
cmp_dir = tempfile.mkdtemp(prefix="crud_robot_cmp_")
|
|
Path(cmp_dir, "code.py").write_text("print('hello')\n", encoding="utf-8")
|
|
|
|
# 1. Read
|
|
content = handler.read(resource=resource, path="code.py")
|
|
if "hello" not in content.text:
|
|
_fail(f"read failed: 'hello' not in '{content.text}'")
|
|
|
|
# 2. Write
|
|
result = handler.write(resource=resource, path="code.py", data=b"print('world')\n")
|
|
if not result.success:
|
|
_fail(f"write failed: {result.message}")
|
|
|
|
# 3. Diff (should detect change since we modified the file)
|
|
diff = handler.diff(resource=resource, other_location=cmp_dir)
|
|
if not diff.has_changes:
|
|
_fail("diff should detect changes after write")
|
|
|
|
# 4. List children
|
|
children = handler.list_children(resource=resource)
|
|
if "code.py" not in children:
|
|
_fail(f"list_children missing 'code.py': {children}")
|
|
|
|
# 5. Discover children (no subdirs expected)
|
|
handler.discover_children(resource=resource)
|
|
# May or may not have children — just ensure no crash
|
|
|
|
print("git-read-write-diff-ok")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dispatcher
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COMMANDS: dict[str, Callable[[], None]] = {
|
|
"fs-read-write-diff": fs_read_write_diff,
|
|
"git-read-write-diff": git_read_write_diff,
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
|
|
print(
|
|
f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
_COMMANDS[sys.argv[1]]()
|