From 86b21e75f7747143c263f6e02309993c8b353d51 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Thu, 23 Jul 2026 08:18:59 +0200 Subject: [PATCH 1/6] add tests for CL patterns --- .github/workflows/test.yml | 29 ++++++++++++++++ requirements-dev.txt | 1 + tests/test_cli.py | 70 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 tests/test_cli.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..885f5f4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test + +on: + push: + branches: ["**"] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Run tests + run: pytest tests/ -v diff --git a/requirements-dev.txt b/requirements-dev.txt index 11778a3..018eb8e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ pep8 flake8 pycodestyle>=2.3.0 +pytest>=7.0 diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..05d851b --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,70 @@ +"""Tests for the verisure CLI option parsing.""" + +from unittest.mock import MagicMock, patch +from click.testing import CliRunner +from verisure.__main__ import cli + + +def invoke(*args): + runner = CliRunner() + return runner.invoke(cli, ['user@example.com', 'password'] + list(args)) + + +@patch('verisure.__main__.Session') +def test_no_query_options(MockSession): + """Invoking without arm-away/arm-home must not raise an arity error.""" + session = MagicMock() + session.login_cookie.return_value = { + 'data': {'account': {'installations': [{'giid': 'abc'}]}}} + session.request.return_value = [] + MockSession.return_value = session + + result = invoke('--arm-state') + assert 'requires at least 1 argument' not in (result.output + str(result.exception)) + + +@patch('verisure.__main__.Session') +def test_arm_away_with_code(MockSession): + """--arm-away with a valid code should be accepted.""" + session = MagicMock() + session.login_cookie.return_value = { + 'data': {'account': {'installations': [{'giid': 'abc'}]}}} + session.request.return_value = [] + MockSession.return_value = session + + result = invoke('--arm-away', '1234') + assert result.exit_code == 0 + + +def test_arm_away_missing_code(): + """--arm-away without a code should fail with a usage error.""" + runner = CliRunner() + # Pass --arm-away as the last argument so there is no code after it + result = runner.invoke(cli, ['--arm-away', 'user@example.com', 'password']) + assert result.exit_code != 0 + + +@patch('verisure.__main__.Session') +def test_arm_home_with_code(MockSession): + """--arm-home with a valid code should be accepted.""" + session = MagicMock() + session.login_cookie.return_value = { + 'data': {'account': {'installations': [{'giid': 'abc'}]}}} + session.request.return_value = [] + MockSession.return_value = session + + result = invoke('--arm-home', '1234') + assert result.exit_code == 0 + + +@patch('verisure.__main__.Session') +def test_flag_option(MockSession): + """A plain flag option like --arm-state should work without arguments.""" + session = MagicMock() + session.login_cookie.return_value = { + 'data': {'account': {'installations': [{'giid': 'abc'}]}}} + session.request.return_value = [] + MockSession.return_value = session + + result = invoke('--arm-state') + assert result.exit_code == 0 From 1d2748f6121115e6f3e0d026cf4dd5c788ef0982 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Thu, 23 Jul 2026 08:24:04 +0200 Subject: [PATCH 2/6] install package before test --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 885f5f4..82e03cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Install dependencies run: | - pip install -r requirements.txt + pip install -e . pip install -r requirements-dev.txt - name: Run tests From 724a80ab0834651abf6c3f0c10a493292883c390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Thu, 23 Jul 2026 08:25:41 +0200 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/test_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 05d851b..2774517 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,8 +40,9 @@ def test_arm_away_missing_code(): """--arm-away without a code should fail with a usage error.""" runner = CliRunner() # Pass --arm-away as the last argument so there is no code after it - result = runner.invoke(cli, ['--arm-away', 'user@example.com', 'password']) + result = runner.invoke(cli, ['user@example.com', 'password', '--arm-away']) assert result.exit_code != 0 + assert 'requires at least 1 argument' in (result.output + str(result.exception)) @patch('verisure.__main__.Session') From 64a9da3b79aa05acb78079a21bd5afd4526a0c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Thu, 23 Jul 2026 08:25:50 +0200 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/test_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 2774517..42cc82b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,7 +19,8 @@ def test_no_query_options(MockSession): session.request.return_value = [] MockSession.return_value = session - result = invoke('--arm-state') + result = invoke() + assert result.exit_code == 0 assert 'requires at least 1 argument' not in (result.output + str(result.exception)) From 4a2393255a957824ae9b654c95a34e11032c0d57 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Thu, 23 Jul 2026 09:03:09 +0200 Subject: [PATCH 5/6] add test for --help --- tests/test_cli.py | 63 ++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 42cc82b..582320b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,10 @@ -"""Tests for the verisure CLI option parsing.""" +"""Tests for the verisure CLI option parsing. + +Three option patterns are generated from Session query methods: + - Flag: no arguments (e.g. --arm-state) + - Required+optional args via QueryOption (e.g. --arm-away CODE [FORCE_ARM]) + - No options passed at all +""" from unittest.mock import MagicMock, patch from click.testing import CliRunner @@ -10,9 +16,29 @@ def invoke(*args): return runner.invoke(cli, ['user@example.com', 'password'] + list(args)) +def test_help_shows_all_option_patterns(): + """--help should render all four option patterns correctly. + + Patterns: + - Flag (no metavar): --arm-state + - Single required arg: --disarm CODE + - Multiple required args (tuple): --door-lock ... + - Required+optional (QueryOption): --arm-away CODE [FORCE_ARM=False] + - Optional-only (QueryOption): --cameras-image-series [LIMIT=50] [OFFSET=0] + """ + runner = CliRunner(env={'COLUMNS': '120'}) + result = runner.invoke(cli, ['--help']) + assert result.exit_code == 0 + assert '--arm-state ' in result.output # flag: no metavar + assert '--disarm CODE' in result.output # single required arg + assert '--door-lock ' in result.output # tuple of required args + assert '--arm-away CODE [FORCE_ARM=False]' in result.output # required + optional + assert '--cameras-image-series [LIMIT=50] [OFFSET=0]' in result.output # optional only + + @patch('verisure.__main__.Session') -def test_no_query_options(MockSession): - """Invoking without arm-away/arm-home must not raise an arity error.""" +def test_no_options_outputs_empty_result(MockSession): + """Pattern: no query options — should succeed and print an empty result.""" session = MagicMock() session.login_cookie.return_value = { 'data': {'account': {'installations': [{'giid': 'abc'}]}}} @@ -21,15 +47,16 @@ def test_no_query_options(MockSession): result = invoke() assert result.exit_code == 0 - assert 'requires at least 1 argument' not in (result.output + str(result.exception)) + assert result.output.strip() == '[]' @patch('verisure.__main__.Session') -def test_arm_away_with_code(MockSession): - """--arm-away with a valid code should be accepted.""" +def test_required_and_optional_args_pattern_with_required_arg(MockSession): + """Pattern: QueryOption with required+optional args — required arg provided should succeed.""" session = MagicMock() session.login_cookie.return_value = { 'data': {'account': {'installations': [{'giid': 'abc'}]}}} + session.request.return_value = [] MockSession.return_value = session @@ -37,31 +64,17 @@ def test_arm_away_with_code(MockSession): assert result.exit_code == 0 -def test_arm_away_missing_code(): - """--arm-away without a code should fail with a usage error.""" +def test_required_and_optional_args_pattern_missing_required_arg(): + """Pattern: QueryOption with required+optional args — missing required arg should fail.""" runner = CliRunner() - # Pass --arm-away as the last argument so there is no code after it result = runner.invoke(cli, ['user@example.com', 'password', '--arm-away']) assert result.exit_code != 0 - assert 'requires at least 1 argument' in (result.output + str(result.exception)) - - -@patch('verisure.__main__.Session') -def test_arm_home_with_code(MockSession): - """--arm-home with a valid code should be accepted.""" - session = MagicMock() - session.login_cookie.return_value = { - 'data': {'account': {'installations': [{'giid': 'abc'}]}}} - session.request.return_value = [] - MockSession.return_value = session - - result = invoke('--arm-home', '1234') - assert result.exit_code == 0 + assert '--arm-away' in result.output @patch('verisure.__main__.Session') -def test_flag_option(MockSession): - """A plain flag option like --arm-state should work without arguments.""" +def test_flag_pattern(MockSession): + """Pattern: flag option with no arguments — should succeed without any argument.""" session = MagicMock() session.login_cookie.return_value = { 'data': {'account': {'installations': [{'giid': 'abc'}]}}} From 475dc1a555c2665c025d0dfddb0f0c38209e9f1e Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Thu, 23 Jul 2026 09:05:14 +0200 Subject: [PATCH 6/6] update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d9247ec..13a3778 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Options: --arm-home CODE [FORCE_ARM=False] Set arm state home --arm-state Read arm state + --arm-state-dry-run Start an arm state dry run to check if + arming requires force + --arm-state-dry-run-status TRANSACTIONID + Poll the status of an arm state dry run --broadband Get broadband status --camera-capture ... Capture a new image from a camera