Files
nisse/nisse-core/tests/test_actions.py
T
dongarandClaude Opus 5.5 e351d0a74e
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m37s
CI / e2e (pull_request) Successful in 4m5s
nisse-core: group modules into packages by architecture section
A pure move; behaviour unchanged.

- models (L9, L7): llm, catalog, probe, render, render_anthropic, context
- log (L7, L6): canonical, views
- state (L8): store (was state), reconcile, worker, cancel, dump
- knowledge (L10, L6.4): vault, memory
- conversation (L5): transport, matrix
- execution (L13): ssh (was execution), testing
- authority (P3, P4, L4): actions
- gitea, oplog, envfile stay at the top until each gains a second module.

Every import, monkeypatch target, script and doc reference rewritten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:16:09 +00:00

122 lines
3.9 KiB
Python

import pytest
from nisse_core.authority.actions import (
CONTAINED,
DANGEROUS,
HARMLESS,
REFUSED,
UNKNOWN,
Action,
Command,
Verdict,
classify_shell,
inside,
resolve,
split_shell,
unwrap,
worst,
)
def test_an_approval_binds_to_the_exact_tool_and_arguments() -> None:
a = Action("run_command", {"command": "git push"})
assert a.key() == Action("run_command", {"command": "git push"}).key()
assert a.key() != Action("run_command", {"command": "git push -f"}).key()
assert a.key() != Action("write_file", {"command": "git push"}).key()
def test_the_worst_verdict_wins() -> None:
verdicts = [
Verdict(HARMLESS, "a", ""),
Verdict(DANGEROUS, "b", ""),
Verdict(UNKNOWN, "c", ""),
]
assert worst(verdicts).rule == "b"
assert worst([Verdict(DANGEROUS, "d", ""), Verdict(REFUSED, "r", "")]).rule == "r"
assert worst([]).effect == HARMLESS
@pytest.mark.parametrize(
("line", "commands"),
[
("pytest -q", [(["pytest", "-q"], [])]),
(
"pytest 2>/dev/null && git status",
[(["pytest"], ["/dev/null"]), (["git", "status"], [])],
),
(
"ls | grep x; echo hi >> log",
[(["ls"], []), (["grep", "x"], []), (["echo", "hi"], ["log"])],
),
("make test 2>&1 | tail -5", [(["make", "test"], []), (["tail", "-5"], [])]),
("echo 'a > b'", [(["echo", "a > b"], [])]),
("cd sub\nls", [(["cd", "sub"], []), (["ls"], [])]),
("ls # a comment", [(["ls"], [])]),
],
)
def test_a_line_splits_into_its_commands(line: str, commands: list) -> None:
assert split_shell(line) == commands
@pytest.mark.parametrize(
"line",
["echo $(whoami)", "echo `id`", "cat <<EOF\nx\nEOF", "(cd /; rm x)", "echo 'open"],
)
def test_what_cant_be_read_statically_is_not_split(line: str) -> None:
assert split_shell(line) is None
@pytest.mark.parametrize(
("argv", "command"),
[
(["sudo", "apt-get", "install", "x"], ["apt-get", "install", "x"]),
(["FOO=1", "BAR=2", "pytest"], ["pytest"]),
(["env", "-i", "A=1", "make"], ["make"]),
(["timeout", "60", "pytest", "-x"], ["pytest", "-x"]),
(["timeout", "-k", "5", "60", "pytest"], ["pytest"]),
(["timeout", "--signal=KILL", "60", "make"], ["make"]),
(["nohup", "python", "serve.py"], ["python", "serve.py"]),
],
)
def test_wrappers_are_seen_through(argv: list[str], command: list[str]) -> None:
assert unwrap(argv) == command
def test_paths_resolve_without_touching_the_filesystem() -> None:
assert resolve("a/../b", "/w") == "/w/b"
assert resolve("/etc/passwd", "/w") == "/etc/passwd"
assert resolve("$HOME/x", "/w") is None
assert resolve("~/x", "/w") is None
assert resolve("x", None) is None
assert inside("/w/b", "/w") and inside("/w", "/w/")
assert not inside("/wx", "/w") and not inside(None, "/w")
def judge(command: Command) -> Verdict:
"""A toy rule set: rm is dangerous outside /w, ls harmless."""
if command.argv[0] == "ls":
return Verdict(HARMLESS, "ls", "")
if command.argv[0] == "rm":
target = resolve(command.argv[-1], command.cwd)
if inside(target, "/w"):
return Verdict(CONTAINED, "rm-inside", "")
return Verdict(DANGEROUS, "rm-outside", "")
return Verdict(UNKNOWN, "no-rule", "")
@pytest.mark.parametrize(
("line", "rule"),
[
("ls && rm x", "rm-inside"),
("cd /tmp && rm x", "rm-outside"),
("cd sub && rm x", "rm-inside"),
("cd && rm x", "rm-outside"), # cd with no argument: home, unknown
("bash -c 'cd /etc; rm passwd'", "rm-outside"),
("sh -c 'ls'", "ls"),
("ls; frobnicate", "no-rule"),
("ls $(rm -rf /)", "unreadable"),
],
)
def test_a_line_is_as_bad_as_its_worst_command(line: str, rule: str) -> None:
assert classify_shell(line, "/w", judge).rule == rule