CI / test (3.11) (push) Canceled after 0s
CI / test (3.12) (push) Canceled after 0s
CI / test (3.13) (push) Canceled after 0s
CI / test (3.14) (push) Canceled after 0s
CI / package (push) Canceled after 0s
CI / precommit-and-security (push) Canceled after 11s
CI / typecheck (push) Canceled after 11s
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from schedls.cli import _parse_environment, _spec_for_new, build_parser, main, split_command
|
|
from schedls.errors import InvalidNameError, UsageError
|
|
from schedls.models import Backend, Scope
|
|
|
|
|
|
def test_split_command() -> None:
|
|
head, tail = split_command(["new", "x", "--timer", "--", "/bin/true", "arg"])
|
|
assert head == ["new", "x", "--timer"]
|
|
assert tail == ["/bin/true", "arg"]
|
|
head, tail = split_command(["show", "x"])
|
|
assert head == ["show", "x"]
|
|
assert tail == []
|
|
|
|
|
|
def test_edit_command_dest_does_not_shadow_subcommand() -> None:
|
|
args = build_parser().parse_args(["edit", "backup", "--command"])
|
|
assert args.command == "edit"
|
|
assert args.replace_command is True
|
|
|
|
|
|
def test_spec_for_new_systemd() -> None:
|
|
args = build_parser().parse_args(["new", "backup", "--timer", "--daily", "02:00", "--persistent"])
|
|
spec = _spec_for_new(args, ["/usr/local/bin/backup", "/srv/data"])
|
|
assert spec.backend is Backend.SYSTEMD
|
|
assert spec.scope is Scope.USER
|
|
assert spec.calendar == ("*-*-* 02:00:00",)
|
|
assert spec.command.argv == ("/usr/local/bin/backup", "/srv/data")
|
|
assert spec.persistent is True
|
|
|
|
|
|
def test_spec_for_new_cron() -> None:
|
|
args = build_parser().parse_args(["new", "cleanup", "--cron", "--cron-expr", "0 4 * * 0"])
|
|
spec = _spec_for_new(args, ["/usr/local/bin/cleanup"])
|
|
assert spec.backend is Backend.CRON
|
|
assert spec.cron_expression == "0 4 * * 0"
|
|
|
|
|
|
def test_spec_for_new_system_scope() -> None:
|
|
args = build_parser().parse_args(["new", "x", "--timer", "--daily", "02:00", "--system"])
|
|
spec = _spec_for_new(args, ["/bin/true"])
|
|
assert spec.scope is Scope.SYSTEM
|
|
|
|
|
|
def test_spec_for_new_invalid_name() -> None:
|
|
args = build_parser().parse_args(["new", "../evil", "--timer", "--daily", "02:00"])
|
|
with pytest.raises(InvalidNameError):
|
|
_spec_for_new(args, ["/bin/true"])
|
|
|
|
|
|
def test_spec_for_new_requires_schedule() -> None:
|
|
args = build_parser().parse_args(["new", "x", "--timer"])
|
|
with pytest.raises(UsageError):
|
|
_spec_for_new(args, ["/bin/true"])
|
|
|
|
|
|
def test_new_without_backend_is_usage_error() -> None:
|
|
assert main(["new", "x", "--daily", "02:00"]) == 2
|
|
|
|
|
|
def test_new_without_name_is_usage_error() -> None:
|
|
assert main(["new", "--timer", "--daily", "02:00"]) == 2
|
|
|
|
|
|
def test_interactive_conflicts_with_json() -> None:
|
|
assert main(["--json", "new", "x", "-i", "--timer", "--daily", "02:00"]) == 2
|
|
|
|
|
|
def test_interactive_requires_terminal(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("sys.stdin", _FakeStdin(isatty=False))
|
|
assert main(["new", "x", "-i", "--timer", "--daily", "02:00"]) == 3
|
|
|
|
|
|
class _FakeStdin:
|
|
def __init__(self, *, isatty: bool) -> None:
|
|
self._isatty = isatty
|
|
|
|
def isatty(self) -> bool:
|
|
return self._isatty
|
|
|
|
|
|
def test_spec_for_new_shell_mode() -> None:
|
|
args = build_parser().parse_args(["new", "x", "--timer", "--daily", "02:00", "--shell", "echo hi | cat"])
|
|
spec = _spec_for_new(args, [])
|
|
assert spec.command.shell is True
|
|
assert spec.command.raw == "echo hi | cat"
|
|
|
|
|
|
def test_spec_for_new_rejects_systemd_flags_for_cron() -> None:
|
|
for extra in (["--env", "A=1"], ["--working-directory", "/srv/data"], ["--persistent"], ["--jitter", "5min"]):
|
|
args = build_parser().parse_args(["new", "x", "--cron", "--daily", "02:00", *extra])
|
|
with pytest.raises(UsageError):
|
|
_spec_for_new(args, ["/bin/true"])
|
|
|
|
|
|
def test_spec_for_new_rejects_cron_expr_for_timer() -> None:
|
|
args = build_parser().parse_args(["new", "x", "--timer", "--cron-expr", "0 4 * * *"])
|
|
with pytest.raises(UsageError):
|
|
_spec_for_new(args, ["/bin/true"])
|
|
|
|
|
|
def test_spec_for_new_rejects_system_scope_for_cron() -> None:
|
|
args = build_parser().parse_args(["new", "x", "--cron", "--daily", "02:00", "--system"])
|
|
with pytest.raises(UsageError):
|
|
_spec_for_new(args, ["/bin/true"])
|
|
|
|
|
|
@pytest.mark.parametrize("value", ["0", "-1", "1000001", "abc"])
|
|
def test_logs_lines_must_be_a_bounded_positive_integer(value: str) -> None:
|
|
with pytest.raises(SystemExit):
|
|
build_parser().parse_args(["logs", "x", "--lines", value])
|
|
|
|
|
|
def test_parse_environment() -> None:
|
|
assert _parse_environment(["A=1", "B=x=y"]) == (("A", "1"), ("B", "x=y"))
|
|
with pytest.raises(UsageError):
|
|
_parse_environment(["NOVALUE"])
|
|
with pytest.raises(UsageError):
|
|
_parse_environment(["1BAD=1"])
|