import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from llamacppctl.cli import build_parser, validate_args # noqa: E402 def parse(argv): parser = build_parser() args = parser.parse_args(argv) validate_args(args, parser) return args def parse_error(argv): parser = build_parser() with pytest.raises(SystemExit): args = parser.parse_args(argv) validate_args(args, parser) def test_requires_one_action(): parse_error([]) def test_actions_are_mutually_exclusive(): parse_error(["--start", "--stop"]) def test_start_is_valid_minimal(): args = parse(["--start"]) assert args.start is True def test_chat_requires_prompt_source(): parse_error(["--chat"]) def test_chat_with_prompt_is_valid(): args = parse(["--chat", "-p", "hello"]) assert args.chat is True assert args.prompt == "hello" def test_stop_rejects_prompt_sources(): parse_error(["--stop", "-p", "hello"]) def test_stop_rejects_system_sources(): parse_error(["--stop", "-s", "system text"]) def test_check_with_system_but_no_prompt_rejected(): parse_error(["--check", "-s", "system text"]) def test_check_with_system_and_prompt_ok(): args = parse(["--check", "-s", "system text", "-p", "hello"]) assert args.check is True def test_mutually_exclusive_system_sources(): parse_error(["--start", "-s", "a", "--system-file", "b.txt"]) def test_mutually_exclusive_prompt_sources(): parse_error(["--start", "-p", "a", "--prompt-url", "https://example.com/p"]) def test_max_input_bytes_must_be_positive(): parse_error(["--start", "--max-input-bytes", "0"]) def test_negative_max_input_bytes_rejected(): parse_error(["--start", "--max-input-bytes", "-5"]) def test_connect_timeout_must_be_positive(): parse_error(["--start", "--connect-timeout", "0"]) def test_read_timeout_must_be_positive(): parse_error(["--start", "--read-timeout", "-1"]) def test_allow_private_url_conflicts_with_allowlist(): parse_error(["--start", "--allow-private-url", "--url-allow-host", "example.com"]) def test_host_port_out_of_range_rejected(): parse_error(["--start", "--host-port", "70000"]) def test_host_port_zero_rejected(): parse_error(["--start", "--host-port", "0"]) def test_container_port_out_of_range_rejected(): parse_error(["--start", "--container-port", "-1"]) def test_container_name_empty_string_rejected(): parse_error(["--start", "--container-name", " "]) def test_container_name_valid(): args = parse(["--start", "--container-name", "my_llm"]) assert args.container_name == "my_llm" def test_boolean_flag_pairs_default_none(): args = parse(["--start"]) assert args.jinja is None assert args.fa is None assert args.kv_unified is None assert args.cont_batching is None assert args.no_context_shift is None def test_boolean_flag_pairs_explicit_true_false(): args = parse(["--start", "--jinja", "--no-fa"]) assert args.jinja is True assert args.fa is False def test_dry_run_flag(): args = parse(["--start", "--dry-run"]) assert args.dry_run is True def test_print_effective_config_flag(): args = parse(["--start", "--print-effective-config"]) assert args.print_effective_config is True def test_reasoning_choice_validated(): parse_error(["--start", "--reasoning", "maybe"]) def test_reasoning_choice_valid(): args = parse(["--start", "--reasoning", "off"]) assert args.reasoning == "off"