From dc3c924dcf081fb73dc1db8350621b395c0a47cf Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 15 Aug 2026 18:29:48 +1000 Subject: [PATCH] feat(tessie): add set_roles command for payer role assignment Verified full command parity against Tessie's live OpenAPI spec (developer.tessie.com/openapi.yaml): every documented POST /command/* and /wake endpoint was already implemented. The one gap was POST /{vin}/roles ("Set Roles"), a Tesla-Business-only command to assign the subscription/charging payer role, which had no library method. --- docs/tessie.md | 3 +++ tesla_fleet_api/tessie/vehicle.py | 14 ++++++++++++ tests/test_tessie_vehicle_params.py | 35 ++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/tessie.md b/docs/tessie.md index 8c33747..6b02d98 100644 --- a/docs/tessie.md +++ b/docs/tessie.md @@ -415,6 +415,9 @@ async def main(): response = await vehicle.delete_driver(id=1) print(response) + response = await vehicle.set_roles(role="SUBSCRIPTION", account_id="555555555") + print(response) + response = await vehicle.get_fleet_telemetry_config() print(response) diff --git a/tesla_fleet_api/tessie/vehicle.py b/tesla_fleet_api/tessie/vehicle.py index 4b0b71c..02139f3 100644 --- a/tesla_fleet_api/tessie/vehicle.py +++ b/tesla_fleet_api/tessie/vehicle.py @@ -1080,6 +1080,20 @@ async def revoke_invitation(self, id: str | int) -> dict[str, Any]: f"{self.vin}/invitations/{id}/revoke", ) + async def set_roles( + self, + role: str, + account_id: str | None = None, + federation_id: str | None = None, + ) -> dict[str, Any]: + """Assign the subscription or charging payer role (requires a Tesla Business account).""" + payload: dict[str, str] = {"role": role} + if account_id is not None: + payload["account_id"] = account_id + if federation_id is not None: + payload["federation_id"] = federation_id + return await self._request(Method.POST, f"{self.vin}/roles", json=payload) + # Fleet Telemetry async def get_fleet_telemetry_config(self) -> dict[str, Any]: """Retrieve telemetry configuration.""" diff --git a/tests/test_tessie_vehicle_params.py b/tests/test_tessie_vehicle_params.py index 7b847e1..4f9ba39 100644 --- a/tests/test_tessie_vehicle_params.py +++ b/tests/test_tessie_vehicle_params.py @@ -250,7 +250,40 @@ async def test_tessie_add_charge_schedule_validates_enabled_times(self) -> None: lon=4.56, ) - async def test_tessie_add_precondition_schedule_uses_documented_parameters(self) -> None: + async def test_set_roles_uses_documented_parameters(self) -> None: + """set_roles should send the documented JSON body with only provided fields.""" + + vehicle, request = self.create_vehicle() + + response = await vehicle.set_roles( + role="SUBSCRIPTION", + account_id="555555555", + ) + + self.assertEqual(response, {"result": True}) + request.assert_awaited_once_with( + Method.POST, + f"{self.VIN}/roles", + json={"role": "SUBSCRIPTION", "account_id": "555555555"}, + ) + + async def test_set_roles_omits_unset_optional_fields(self) -> None: + """set_roles should omit account_id/federation_id when not provided.""" + + vehicle, request = self.create_vehicle() + + response = await vehicle.set_roles(role="CHARGING") + + self.assertEqual(response, {"result": True}) + request.assert_awaited_once_with( + Method.POST, + f"{self.VIN}/roles", + json={"role": "CHARGING"}, + ) + + async def test_tessie_add_precondition_schedule_uses_documented_parameters( + self, + ) -> None: """Precondition schedule requests should follow the Tessie schema.""" vehicle, request = self.create_vehicle()