"""CLI option surface tests: renamed branch-separation flag, short aliases for every long option, and dry-run output shape (audit follow-up by the maintainer: rename --normalize-to-one, add short options, drop the redundant top-level Transition line).""" from __future__ import annotations import dataclasses from typing import Any, Optional import pytest from beta_spectrum import BranchConfig, SpectrumConfig from beta_spectrum.cli import _build_parser, _print_dry_run_output pytestmark = pytest.mark.quality def make_config() -> SpectrumConfig: return SpectrumConfig( Z_parent=59, Z_daughter=60, A_number=144, endpoint_MeV=2.98, transition_type="F1", e_step_MeV=0.001, branches=[ BranchConfig(endpoint_MeV=2.98, transition_type="F1", intensity=0.9), BranchConfig(endpoint_MeV=2.1, transition_type="A", intensity=0.1), ], ) class TestSeparateBranchesRename: """--normalize-to-one was renamed to --separate-branches (no alias).""" def test_flag_parses_true(self) -> None: parser = _build_parser() args = parser.parse_args(["input.json", "--separate-branches"]) assert args.separate_branches is True def test_flag_default_is_none(self) -> None: parser = _build_parser() args = parser.parse_args(["input.json"]) assert args.separate_branches is None def test_old_flag_is_rejected(self) -> None: parser = _build_parser() with pytest.raises(SystemExit): parser.parse_args(["input.json", "--normalize-to-one"]) def test_config_field_renamed(self) -> None: names = {f.name for f in dataclasses.fields(SpectrumConfig)} assert "separate_branches" in names assert "normalize_to_one" not in names def test_config_old_kwarg_rejected(self) -> None: kwargs: dict[str, Any] = { "Z_parent": 15, "Z_daughter": 16, "A_number": 32, "endpoint_MeV": 1.7103, "transition_type": "A", "normalize_to_one": True, } with pytest.raises(TypeError): SpectrumConfig(**kwargs) # (short, long, value-or-None-for-store_true) _SHORT_EQUIV = [ ("-N", "--nuclide", "Cs137"), ("-i", "--input", "in.json"), ("-s", "--e-step", "0.01"), ("-m", "--neutrino-mass", "0.15"), ("-o", "--output", "out.csv"), ("-p", "--plot", "plot.png"), ("-L", "--log-file", "calc.log"), ("-d", "--dry-run", None), ("-c", "--intensity-cutoff", "0.5"), ("-V", "--csv-verbosity", "concise"), ("-S", "--csv-separator", "tab"), ("-b", "--separate-branches", None), ] class TestShortOptions: """Every long option gains a single-letter alias (only -v/-q existed).""" @pytest.mark.parametrize( "short,long,value", _SHORT_EQUIV, ids=[s.lstrip("-") for s, _, _ in _SHORT_EQUIV], ) def test_short_matches_long( self, short: str, long: str, value: Optional[str] ) -> None: parser = _build_parser() argv_long = [long] if value is None else [long, value] argv_short = [short] if value is None else [short, value] args_long = parser.parse_args(["Tc99"] + argv_long) args_short = parser.parse_args(["Tc99"] + argv_short) dest = long.lstrip("-").replace("-", "_") assert getattr(args_short, dest) == getattr(args_long, dest) assert getattr(args_long, dest) is not None or value is None def test_existing_shorts_unchanged(self) -> None: parser = _build_parser() args = parser.parse_args(["Tc99", "-v", "-q"]) assert args.verbose == 1 assert args.quiet is True class TestDryRunOutputShape: """The top-level Transition line is redundant with the per-branch list (for multi-branch runs) and was removed.""" def test_multi_branch_dry_run_no_top_level_transition(self, capsys: Any) -> None: _print_dry_run_output(make_config(), "paceENSDF") out = capsys.readouterr().out assert "Transition:" not in out assert "transition=F1" in out assert "transition=A" in out assert "Multi-branch mode: 2 branches" in out def test_single_branch_dry_run_reports_endpoint(self, capsys: Any) -> None: config = SpectrumConfig( Z_parent=15, Z_daughter=16, A_number=32, endpoint_MeV=1.7103, transition_type="A", ) _print_dry_run_output(config, "json") out = capsys.readouterr().out assert "Transition:" not in out assert "Mode: single-branch" in out