Repository: bimmerconnected/bimmer_connected
Pull request #624 (2024-07-01 17:15:50)

Add basic CLI tests
## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->
Add tests for CLI (available at all, `status` and `fingerprint` commands).

## Type of change
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (which adds functionality to this library)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [x] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [x] The code change is tested and works locally.
- [x] Tests have been added to verify that the new code works.



Patch:
diff --git a/bimmer_connected/cli.py b/bimmer_connected/cli.py
index 9ad7a03..f4bda1f 100644
--- a/bimmer_connected/cli.py
+++ b/bimmer_connected/cli.py
@@ -138,7 +138,7 @@ async def get_status(args) -> None:
 
     if args.json:
         if args.vin:
-            print(json.dumps(account.get_vehicle(args.vin), cls=MyBMWJSONEncoder))
+            print(json.dumps(get_vehicle_or_return(account, args.vin), cls=MyBMWJSONEncoder))
         else:
             print(json.dumps(account.vehicles, cls=MyBMWJSONEncoder))
     else:
@@ -338,7 +338,11 @@ def main():
         logging.getLogger("asyncio").setLevel(logging.WARNING)
 
     loop = asyncio.get_event_loop()
-    loop.run_until_complete(args.func(args))
+    try:
+        loop.run_until_complete(args.func(args))
+    except Exception as ex:  # pylint: disable=broad-except
+        sys.stderr.write(f"{type(ex).__name__}: {ex}\n")
+        sys.exit(1)
 
 
 if __name__ == "__main__":


Test patch:
diff --git a/bimmer_connected/tests/test_cli.py b/bimmer_connected/tests/test_cli.py
new file mode 100644
index 0000000..6f5eec8
--- /dev/null
+++ b/bimmer_connected/tests/test_cli.py
@@ -0,0 +1,138 @@
+import json
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+
+import pytest
+
+import bimmer_connected.cli
+
+from . import get_fingerprint_count
+
+ARGS_USER_PW_REGION = ["myuser", "mypassword", "rest_of_world"]
+FIXTURE_CLI_HELP = "A simple executable to use and test the library."
+
+
+def test_run_entrypoint():
+    """Test if the entrypoint is installed correctly."""
+    result = subprocess.run(["bimmerconnected", "--help"], capture_output=True, text=True)
+
+    assert FIXTURE_CLI_HELP in result.stdout
+    assert result.returncode == 0
+
+
+def test_run_module():
+    """Test if the module can be run as a python module."""
+    result = subprocess.run(["python", "-m", "bimmer_connected.cli", "--help"], capture_output=True, text=True)
+
+    assert FIXTURE_CLI_HELP in result.stdout
+    assert result.returncode == 0
+
+
+@pytest.mark.usefixtures("bmw_fixture")
+@pytest.mark.parametrize(
+    ("vin", "expected_count"),
+    [
+        ("WBA00000000000F31", 1),
+        ("WBA00000000000F31,WBA00000000DEMO03", 0),
+        ("WBA00000000000Z99", 0),
+    ],
+)
+def test_status_json_filtered(capsys: pytest.CaptureFixture, vin, expected_count):
+    """Test the status command JSON output filtered by VIN."""
+
+    sys.argv = ["bimmerconnected", "status", "-j", "-v", vin, *ARGS_USER_PW_REGION]
+    try:
+        bimmer_connected.cli.main()
+    except SystemExit:
+        pass
+    result = capsys.readouterr()
+
+    if expected_count == 1:
+        result_json = json.loads(result.out)
+        assert isinstance(result_json, dict)
+        assert result_json["vin"] == vin
+    else:
+        assert "Error: Could not find vehicle" in result.err
+
+
+@pytest.mark.usefixtures("bmw_fixture")
+def test_status_json_unfiltered(capsys: pytest.CaptureFixture):
+    """Test the status command JSON output filtered by VIN."""
+
+    sys.argv = ["bimmerconnected", "status", "-j", *ARGS_USER_PW_REGION]
+    bimmer_connected.cli.main()
+    result = capsys.readouterr()
+
+    result_json = json.loads(result.out)
+    assert isinstance(result_json, list)
+    assert len(result_json) == get_fingerprint_count("states")
+
+
+@pytest.mark.usefixtures("bmw_fixture")
+@pytest.mark.parametrize(
+    ("vin", "expected_count"),
+    [
+        ("WBA00000000000F31", 1),
+        ("WBA00000000000F31,WBA00000000DEMO03", 0),
+        ("WBA00000000000Z99", 0),
+    ],
+)
+def test_status_filtered(capsys: pytest.CaptureFixture, vin, expected_count):
+    """Test the status command text output filtered by VIN."""
+
+    sys.argv = ["bimmerconnected", "status", "-v", vin, *ARGS_USER_PW_REGION]
+    try:
+        bimmer_connected.cli.main()
+    except SystemExit:
+        pass
+    result = capsys.readouterr()
+
+    assert f"Found {get_fingerprint_count('states')} vehicles" in result.out
+
+    if expected_count == 1:
+        assert f"VIN: {vin}" in result.out
+        assert result.out.count("VIN: ") == expected_count
+    else:
+        assert result.out.count("VIN: ") == expected_count
+
+
+@pytest.mark.usefixtures("bmw_fixture")
+def test_status_unfiltered(capsys: pytest.CaptureFixture):
+    """Test the status command text output filtered by VIN."""
+
+    sys.argv = ["bimmerconnected", "status", *ARGS_USER_PW_REGION]
+    bimmer_connected.cli.main()
+    result = capsys.readouterr()
+
+    assert f"Found {get_fingerprint_count('states')} vehicles" in result.out
+    assert result.out.count("VIN: ") == get_fingerprint_count("states")
+
+
+@pytest.mark.usefixtures("bmw_fixture")
+@pytest.mark.usefixtures("bmw_log_all_responses")
+def test_fingerprint(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch):
+    """Test the fingerprint command."""
+
+    with tempfile.TemporaryDirectory() as tmpdirname:
+        tmp_path = Path(tmpdirname)
+        monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
+
+        sys.argv = ["bimmerconnected", "fingerprint", *ARGS_USER_PW_REGION]
+        bimmer_connected.cli.main()
+        result = capsys.readouterr()
+
+        assert "fingerprint of the vehicles written to" in result.out
+
+        files = list(tmp_path.rglob("*"))
+        json_files = [f for f in files if f.suffix == ".json"]
+        txt_files = [f for f in files if f.suffix == ".txt"]
+
+        assert len(json_files) == (
+            get_fingerprint_count("vehicles")
+            + get_fingerprint_count("profiles")
+            + get_fingerprint_count("states")
+            + get_fingerprint_count("charging_settings")
+        )
+        assert len(txt_files) == 0