Skip to content

Commit

Permalink
Merge pull request #241 from jschlyter/test_models
Browse files Browse the repository at this point in the history
Update data model and tests
  • Loading branch information
jschlyter authored Nov 18, 2024
2 parents 2e6616a + f62c2f0 commit d4c0c26
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 7 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,27 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
pytest:
needs: ruff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Get full python version
id: full-python-version
run: |
echo version=$(python -c "import sys, platform; print('.'.join(str(v) for v in sys.version_info[:3]) + '_' + platform.machine())") >> $GITHUB_OUTPUT
- name: Set up cache
uses: actions/cache@v4
with:
path: .venv
key: ${{ runner.os }}-venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('requirements.txt') }}
- name: Create virtual environment
run: python3 -m venv .venv
- name: Install dependencies
run: .venv/bin/pip3 install -r requirements.txt
- name: Execute tests
run: make PYTEST=.venv/bin/pytest test
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SOURCE= custom_components

PYTEST= pytest

all:

Expand All @@ -11,4 +11,4 @@ reformat:
ruff format $(SOURCE)

test:
PYTHONPATH=custom_components pytest -vv
PYTHONPATH=$(SOURCE) $(PYTEST) -vv tests
21 changes: 16 additions & 5 deletions custom_components/polestar_api/pypolestar/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ class CarInformationData(CarBaseInformation):
registration_no: str | None
registration_date: date | None
factory_complete_date: date | None
model_name: str | None
image_url: str | None
battery: str | None
torque: str | None
software_version: str | None

@classmethod
def from_dict(cls, data: GqlDict) -> Self:
if not isinstance(data, dict):
raise TypeError

return cls(
vin=get_field_name_str("vin", data),
internal_vehicle_identifier=get_field_name_str(
Expand All @@ -59,6 +63,7 @@ def from_dict(cls, data: GqlDict) -> Self:
registration_no=get_field_name_str("registrationNo", data),
registration_date=get_field_name_date("registrationDate", data),
factory_complete_date=get_field_name_date("factoryCompleteDate", data),
model_name=get_field_name_str("content/model/name", data),
image_url=get_field_name_str("content/images/studio/url", data),
battery=get_field_name_str("content/specification/battery", data),
torque=get_field_name_str("content/specification/torque", data),
Expand All @@ -77,6 +82,9 @@ class CarOdometerData(CarBaseInformation):

@classmethod
def from_dict(cls, data: GqlDict) -> Self:
if not isinstance(data, dict):
raise TypeError

return cls(
average_speed_km_per_hour=get_field_name_float(
"averageSpeedKmPerHour", data
Expand Down Expand Up @@ -106,18 +114,21 @@ class CarBatteryData(CarBaseInformation):

@classmethod
def from_dict(cls, data: GqlDict) -> Self:
if not isinstance(data, dict):
raise TypeError

try:
charger_connection_status = ChargingConnectionStatus(
charger_connection_status = ChargingConnectionStatus[
get_field_name_str("chargerConnectionStatus", data)
)
except ValueError:
]
except KeyError:
charger_connection_status = (
ChargingConnectionStatus.CHARGER_CONNECTION_STATUS_UNSPECIFIED
)

try:
charging_status = ChargingStatus(get_field_name_str("chargingStatus", data))
except ValueError:
charging_status = ChargingStatus[get_field_name_str("chargingStatus", data)]
except KeyError:
charging_status = ChargingStatus.CHARGING_STATUS_UNSPECIFIED

return cls(
Expand Down
53 changes: 53 additions & 0 deletions tests/data/polestar3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"getBatteryData": {
"averageEnergyConsumptionKwhPer100Km": 22.4,
"batteryChargeLevelPercentage": 34,
"chargerConnectionStatus": "CHARGER_CONNECTION_STATUS_DISCONNECTED",
"chargingCurrentAmps": null,
"chargingPowerWatts": null,
"chargingStatus": "CHARGING_STATUS_IDLE",
"estimatedChargingTimeMinutesToTargetDistance": 0,
"estimatedChargingTimeToFullMinutes": 0,
"estimatedDistanceToEmptyKm": 150,
"estimatedDistanceToEmptyMiles": 90,
"eventUpdatedTimestamp": {
"iso": "2024-11-11T17:47:13.000Z",
"unix": "1731347233"
}
},
"getConsumerCarsV2": {
"content": {
"images": {
"studio": {
"url": "https://cas.polestar.com/image/dynamic/MY24_2207/359/summary-transparent-v2/EA/1/72300/R80000/R102/LR02/EV02/K503/JB07/SW01/_/ET01/default.png?market=se"
}
},
"model": {
"name": "Polestar 3"
},
"specification": {
"battery": "400V lithium-ion battery, 111 kWh capacity, 17 modules",
"torque": "840 Nm / 620 lbf-ft"
}
},
"factoryCompleteDate": "2024-04-16",
"internalVehicleIdentifier": "1aaeb452-700e-46f3-9899-395b6219c8a6",
"registrationDate": null,
"registrationNo": "MLB007",
"software": {
"version": null,
"versionTimestamp": null
},
"vin": "YSMYKEAE7RB000000"
},
"getOdometerData": {
"averageSpeedKmPerHour": 42,
"eventUpdatedTimestamp": {
"iso": "2024-11-11T15:15:16.000Z",
"unix": "1731338116"
},
"odometerMeters": 2001000,
"tripMeterAutomaticKm": 4.2,
"tripMeterManualKm": 1984.0
}
}
105 changes: 105 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import json
from datetime import date, datetime, timezone
from pathlib import Path

import pytest
from polestar_api.pypolestar.models import (
CarBatteryData,
CarInformationData,
CarOdometerData,
ChargingConnectionStatus,
ChargingStatus,
)

DATADIR = Path(__file__).parent.resolve() / "data"


@pytest.fixture
def polestar3_test_data():
try:
with open(DATADIR / "polestar3.json") as fp:
return json.load(fp)
except FileNotFoundError as e:
pytest.skip(f"Test data file not found: {e.filename}")
except json.JSONDecodeError as e:
pytest.skip(f"Invalid JSON in test data file: {e}")


def test_car_information_data(polestar3_test_data):
data = CarInformationData.from_dict(polestar3_test_data["getConsumerCarsV2"])
# Verify expected attributes
assert data is not None
assert isinstance(data, CarInformationData)
assert data.vin == "YSMYKEAE7RB000000"
assert data.internal_vehicle_identifier == "1aaeb452-700e-46f3-9899-395b6219c8a6"
assert data.registration_no == "MLB007"
assert data.registration_date is None
assert data.factory_complete_date == date(year=2024, month=4, day=16)
assert data.model_name == "Polestar 3"
assert (
data.image_url
== "https://cas.polestar.com/image/dynamic/MY24_2207/359/summary-transparent-v2/EA/1/72300/R80000/R102/LR02/EV02/K503/JB07/SW01/_/ET01/default.png?market=se"
)
assert data.battery == "400V lithium-ion battery, 111 kWh capacity, 17 modules"
assert data.torque == "840 Nm / 620 lbf-ft"
assert data.software_version is None


def test_car_information_data_invalid():
with pytest.raises(KeyError):
CarInformationData.from_dict({}) # Test with empty dict
with pytest.raises(TypeError):
CarInformationData.from_dict(None) # Test with None


def test_car_battery_data(polestar3_test_data):
data = CarBatteryData.from_dict(polestar3_test_data["getBatteryData"])
assert data is not None
assert isinstance(data, CarBatteryData)
assert data.average_energy_consumption_kwh_per_100km == 22.4
assert data.battery_charge_level_percentage == 34
assert (
data.charger_connection_status
== ChargingConnectionStatus.CHARGER_CONNECTION_STATUS_DISCONNECTED
)
assert data.charging_current_amps == 0
assert data.charging_power_watts == 0
assert data.charging_status == ChargingStatus.CHARGING_STATUS_IDLE
assert data.estimated_charging_time_minutes_to_target_distance == 0
assert data.estimated_charging_time_to_full_minutes == 0
assert data.estimated_distance_to_empty_km == 150
assert data.event_updated_timestamp == datetime(
year=2024,
month=11,
day=11,
hour=17,
minute=47,
second=13,
tzinfo=timezone.utc,
)
assert data.event_updated_timestamp.timestamp() == 1731347233


def test_car_battery_data_invalid():
with pytest.raises(KeyError):
CarBatteryData.from_dict({})
with pytest.raises(TypeError):
CarBatteryData.from_dict(None)


def test_car_odometer_data(polestar3_test_data):
data = CarOdometerData.from_dict(polestar3_test_data["getOdometerData"])
assert data is not None
assert isinstance(data, CarOdometerData)
assert data.average_speed_km_per_hour == 42.0
assert data.event_updated_timestamp.timestamp() == 1731338116
assert data.trip_meter_automatic_km == 4.2
assert data.trip_meter_manual_km == 1984.0
assert data.odometer_meters == 2001000


def test_car_odometer_data_invalid():
with pytest.raises(KeyError):
CarOdometerData.from_dict({})
with pytest.raises(TypeError):
CarOdometerData.from_dict(None)

0 comments on commit d4c0c26

Please sign in to comment.