Repository: bimmerconnected/bimmer_connected
Pull request #690 (2024-11-29 19:14:23)

Store bmw-session-id for CLI use
## 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.
-->
Keep session_id across muliple CLI runs. This is closer to HA behavior (session id resets only during HA restart).

## 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)
- [ ] 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: #686, #681

## 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/account.py b/bimmer_connected/account.py
index 725617b..61e88f1 100644
--- a/bimmer_connected/account.py
+++ b/bimmer_connected/account.py
@@ -165,7 +165,11 @@ class MyBMWAccount:
         self.config.observer_position = GPSPosition.init_nonempty(latitude=latitude, longitude=longitude)
 
     def set_refresh_token(
-        self, refresh_token: str, gcid: Optional[str] = None, access_token: Optional[str] = None
+        self,
+        refresh_token: str,
+        gcid: Optional[str] = None,
+        access_token: Optional[str] = None,
+        session_id: Optional[str] = None,
     ) -> None:
         """Overwrite the current value of the MyBMW tokens and GCID (if available)."""
         self.config.authentication.refresh_token = refresh_token
@@ -173,6 +177,8 @@ class MyBMWAccount:
             self.config.authentication.gcid = gcid
         if access_token:
             self.config.authentication.access_token = access_token
+        if session_id:
+            self.config.authentication.session_id = session_id
 
     @staticmethod
     def get_stored_responses() -> List[AnonymizedResponse]:
diff --git a/bimmer_connected/cli.py b/bimmer_connected/cli.py
index ce62b48..e554921 100644
--- a/bimmer_connected/cli.py
+++ b/bimmer_connected/cli.py
@@ -359,6 +359,7 @@ def main():
                 "refresh_token": account.config.authentication.refresh_token,
                 "gcid": account.config.authentication.gcid,
                 "access_token": account.config.authentication.access_token,
+                "session_id": account.config.authentication.session_id,
             }
         ),
     )


Test patch:
diff --git a/bimmer_connected/tests/test_cli.py b/bimmer_connected/tests/test_cli.py
index acc63c5..3a3ed89 100644
--- a/bimmer_connected/tests/test_cli.py
+++ b/bimmer_connected/tests/test_cli.py
@@ -153,7 +153,7 @@ def test_oauth_store_credentials(cli_home_dir: Path, bmw_fixture: respx.Router):
     assert (cli_home_dir / ".bimmer_connected.json").exists() is True
     oauth_storage = json.loads((cli_home_dir / ".bimmer_connected.json").read_text())
 
-    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid"}
+    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid", "session_id"}
 
 
 # @pytest.mark.usefixtures("bmw_fixture")
@@ -165,6 +165,7 @@ def test_oauth_load_credentials(cli_home_dir: Path, bmw_fixture: respx.Router):
         "access_token": "demo_access_token",
         "refresh_token": "demo_refresh_token",
         "gcid": "demo_gcid",
+        "session_id": "demo_session_id",
     }
 
     (cli_home_dir / ".bimmer_connected.json").write_text(json.dumps(demo_oauth_data))
@@ -176,16 +177,18 @@ def test_oauth_load_credentials(cli_home_dir: Path, bmw_fixture: respx.Router):
 
     assert bmw_fixture.routes["token"].call_count == 0
     assert bmw_fixture.routes["vehicles"].calls[0].request.headers["authorization"] == "Bearer demo_access_token"
+    assert bmw_fixture.routes["vehicles"].calls[0].request.headers["bmw-session-id"] == "demo_session_id"
 
     assert (cli_home_dir / ".bimmer_connected.json").exists() is True
     oauth_storage = json.loads((cli_home_dir / ".bimmer_connected.json").read_text())
 
-    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid"}
+    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid", "session_id"}
 
     # no change as the old tokens are still valid
     assert oauth_storage["refresh_token"] == demo_oauth_data["refresh_token"]
     assert oauth_storage["access_token"] == demo_oauth_data["access_token"]
     assert oauth_storage["gcid"] == demo_oauth_data["gcid"]
+    assert oauth_storage["session_id"] == demo_oauth_data["session_id"]
 
 
 @pytest.mark.usefixtures("bmw_fixture")
@@ -219,7 +222,7 @@ def test_oauth_store_credentials_path(cli_home_dir: Path, tmp_path_factory: pyte
 
     oauth_storage = json.loads((new_folder / filepath).read_text())
 
-    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid"}
+    assert set(oauth_storage.keys()) == {"access_token", "refresh_token", "gcid", "session_id"}
 
 
 @pytest.mark.usefixtures("bmw_fixture")