File: test_vehicle.py

package info (click to toggle)
python-bimmer-connected 0.16.3-1.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,304 kB
  • sloc: python: 4,469; makefile: 15
file content (322 lines) | stat: -rw-r--r-- 10,516 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
"""Tests for MyBMWVehicle."""

import pytest
import respx

from bimmer_connected.const import ATTR_ATTRIBUTES, ATTR_STATE, CarBrands
from bimmer_connected.models import GPSPosition, StrEnum, VehicleDataBase
from bimmer_connected.vehicle import VehicleViewDirection
from bimmer_connected.vehicle.const import DriveTrainType
from bimmer_connected.vehicle.reports import CheckControlMessageReport

from . import (
    VIN_F31,
    VIN_G01,
    VIN_G20,
    VIN_G26,
    VIN_G70,
    VIN_I01_NOREX,
    VIN_I01_REX,
    VIN_I20,
    VIN_J29,
    get_deprecation_warning_count,
)
from .conftest import prepare_account_with_vehicles

ATTRIBUTE_MAPPING = {
    "remainingFuel": "remaining_fuel",
    "position": "gps_position",
    "cbsData": "condition_based_services",
    "checkControlMessages": "check_control_messages",
    "doorLockState": "door_lock_state",
    "updateReason": "last_update_reason",
    "chargingLevelHv": "charging_level_hv",
    "chargingStatus": "charging_status",
    "maxRangeElectric": "max_range_electric",
    "remainingRangeElectric": "remaining_range_electric",
    "parkingLight": "parking_lights",
    "remainingRangeFuel": "remaining_range_fuel",
    "updateTime": "timestamp",
    "chargingTimeRemaining": "charging_time_remaining",
}


@pytest.mark.asyncio
async def test_drive_train(caplog, bmw_fixture: respx.Router):
    """Tests around drive_train attribute."""
    account = await prepare_account_with_vehicles()
    vehicle = account.get_vehicle(VIN_F31)
    assert vehicle.drive_train == DriveTrainType.COMBUSTION

    vehicle = account.get_vehicle(VIN_G01)
    assert vehicle.drive_train == DriveTrainType.PLUGIN_HYBRID

    vehicle = account.get_vehicle(VIN_G26)
    assert vehicle.drive_train == DriveTrainType.ELECTRIC

    vehicle = account.get_vehicle(VIN_I01_NOREX)
    assert vehicle.drive_train == DriveTrainType.ELECTRIC

    vehicle = account.get_vehicle(VIN_I01_REX)
    assert vehicle.drive_train == DriveTrainType.ELECTRIC_WITH_RANGE_EXTENDER

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_parsing_attributes(caplog, bmw_fixture: respx.Router):
    """Test parsing different attributes of the vehicle."""
    account = await prepare_account_with_vehicles()

    for vehicle in account.vehicles:
        print(vehicle.name)
        assert vehicle.drive_train is not None
        assert vehicle.name is not None
        assert isinstance(vehicle.brand, CarBrands)
        assert vehicle.has_combustion_drivetrain is not None
        assert vehicle.has_electric_drivetrain is not None
        assert vehicle.drive_train_attributes is not None
        assert vehicle.is_charging_plan_supported is not None

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_drive_train_attributes(caplog, bmw_fixture: respx.Router):
    """Test parsing different attributes of the vehicle."""
    account = await prepare_account_with_vehicles()

    vehicle_drivetrains = {
        VIN_F31: (True, False),
        VIN_G01: (True, True),
        VIN_G20: (True, False),
        VIN_G26: (False, True),
        VIN_G70: (False, True),
        VIN_I01_NOREX: (False, True),
        VIN_I01_REX: (True, True),
        VIN_I20: (False, True),
        VIN_J29: (True, False),
    }

    for vehicle in account.vehicles:
        assert vehicle_drivetrains[vehicle.vin][0] == vehicle.has_combustion_drivetrain
        assert vehicle_drivetrains[vehicle.vin][1] == vehicle.has_electric_drivetrain

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_parsing_of_lsc_type(caplog, bmw_fixture: respx.Router):
    """Test parsing the lsc type field."""
    account = await prepare_account_with_vehicles()

    for vehicle in account.vehicles:
        assert vehicle.lsc_type is not None

    assert len(get_deprecation_warning_count(caplog)) == 0


def test_car_brand(caplog, bmw_fixture: respx.Router):
    """Test CarBrand enum."""
    assert CarBrands("BMW") == CarBrands("bmw")

    with pytest.raises(ValueError):
        CarBrands("Audi")

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_get_is_tracking_enabled(caplog, bmw_fixture: respx.Router):
    """Test setting observer position."""
    account = await prepare_account_with_vehicles()
    vehicle = account.get_vehicle(VIN_I01_REX)
    assert vehicle.is_vehicle_tracking_enabled is False

    vehicle = account.get_vehicle(VIN_G20)
    assert vehicle.is_vehicle_tracking_enabled is True

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_available_attributes(caplog, bmw_fixture: respx.Router):
    """Check that available_attributes returns exactly the arguments we have in our test data."""
    account = await prepare_account_with_vehicles()

    vehicle = account.get_vehicle(VIN_F31)
    assert vehicle.available_attributes == ["gps_position", "vin"]

    vehicle = account.get_vehicle(VIN_G01)
    assert vehicle.available_attributes == [
        "gps_position",
        "vin",
        "remaining_range_total",
        "mileage",
        "charging_time_remaining",
        "charging_start_time",
        "charging_end_time",
        "charging_time_label",
        "charging_status",
        "connection_status",
        "remaining_battery_percent",
        "remaining_range_electric",
        "last_charging_end_result",
        "ac_current_limit",
        "charging_target",
        "charging_mode",
        "charging_preferences",
        "is_pre_entry_climatization_enabled",
        "remaining_fuel",
        "remaining_range_fuel",
        "remaining_fuel_percent",
        "condition_based_services",
        "check_control_messages",
        "door_lock_state",
        "timestamp",
        "lids",
        "windows",
    ]

    vehicle = account.get_vehicle(VIN_G26)
    assert vehicle.available_attributes == [
        "gps_position",
        "vin",
        "remaining_range_total",
        "mileage",
        "charging_time_remaining",
        "charging_start_time",
        "charging_end_time",
        "charging_time_label",
        "charging_status",
        "connection_status",
        "remaining_battery_percent",
        "remaining_range_electric",
        "last_charging_end_result",
        "ac_current_limit",
        "charging_target",
        "charging_mode",
        "charging_preferences",
        "is_pre_entry_climatization_enabled",
        "condition_based_services",
        "check_control_messages",
        "door_lock_state",
        "timestamp",
        "lids",
        "windows",
    ]

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_vehicle_image(caplog, bmw_fixture: respx.Router):
    """Test vehicle image request."""
    vehicle = (await prepare_account_with_vehicles()).get_vehicle(VIN_G01)

    bmw_fixture.get(
        path="/eadrax-ics/v5/presentation/vehicles/images",
        params={"carView": "FrontView"},
        headers={"accept": "image/png", "bmw-app-vehicle-type": "connected", "bmw-vin": VIN_G01},
    ).respond(200, content="png_image")
    assert await vehicle.get_vehicle_image(VehicleViewDirection.FRONT) == b"png_image"

    assert len(get_deprecation_warning_count(caplog)) == 0


@pytest.mark.asyncio
async def test_no_timestamp(bmw_fixture: respx.Router):
    """Test no timestamp available."""
    vehicle = (await prepare_account_with_vehicles()).get_vehicle(VIN_F31)
    vehicle.data[ATTR_STATE].pop("lastFetched", None)
    vehicle.data[ATTR_ATTRIBUTES].pop("lastFetched", None)

    assert vehicle.timestamp is None


def test_strenum(caplog):
    """Tests StrEnum."""

    class TestEnum(StrEnum):
        """Test StrEnum."""

        HELLO = "HELLO"

    assert TestEnum("hello") == TestEnum.HELLO
    assert TestEnum("HELLO") == TestEnum.HELLO

    with pytest.raises(ValueError):
        TestEnum("WORLD")

    class TestEnumUnkown(StrEnum):
        """Test StrEnum with UNKNOWN value."""

        HELLO = "HELLO"
        UNKNOWN = "UNKNOWN"

    assert TestEnumUnkown("hello") == TestEnumUnkown.HELLO
    assert TestEnumUnkown("HELLO") == TestEnumUnkown.HELLO

    assert len([r for r in caplog.records if r.levelname == "WARNING"]) == 0
    assert TestEnumUnkown("WORLD") == TestEnumUnkown.UNKNOWN
    assert len([r for r in caplog.records if r.levelname == "WARNING"]) == 1


def test_vehiclebasedata():
    """Tests VehicleBaseData."""
    with pytest.raises(NotImplementedError):
        VehicleDataBase._parse_vehicle_data({})

    # CheckControlMessageReport does not override parent methods from_vehicle_data()
    ccmr = CheckControlMessageReport.from_vehicle_data(
        {"state": {"checkControlMessages": [{"severity": "LOW", "type": "ENGINE_OIL"}]}}
    )
    assert len(ccmr.messages) == 1
    assert ccmr.has_check_control_messages is False


def test_gpsposition():
    """Tests around GPSPosition."""
    pos = GPSPosition(1.0, 2.0)
    assert pos == GPSPosition(1, 2)
    assert pos == {"latitude": 1.0, "longitude": 2.0}
    assert pos == (1, 2)
    assert pos != "(1, 2)"
    assert pos[0] == 1

    with pytest.raises(TypeError, match="GPSPosition requires either none or both arguments set"):
        GPSPosition(1, None)

    with pytest.raises(TypeError, match="GPSPosition requires either none or both arguments set"):
        GPSPosition(None, 2)

    with pytest.raises(TypeError, match="GPSPosition requires either none or both arguments set"):
        GPSPosition(1, None)

    with pytest.raises(TypeError, match="'longitude' not of type"):
        GPSPosition(0, "49.7")

    with pytest.raises(ValueError, match="'latitude' must be between -90 and 90"):
        GPSPosition(91, 0)

    with pytest.raises(ValueError, match="'longitude' must be between -180 and 180"):
        GPSPosition(90, 181)


@pytest.mark.asyncio
async def test_headunit_data(caplog, bmw_fixture: respx.Router):
    """Test if the parsing of headunit is working."""

    status = (await prepare_account_with_vehicles()).get_vehicle(VIN_I20).headunit

    assert status.idrive_version == "ID8"
    assert status.headunit_type == "MGU"
    assert status.software_version == "07/2021.00"

    status = (await prepare_account_with_vehicles()).get_vehicle(VIN_F31).headunit

    assert status.idrive_version == "ID4"
    assert status.headunit_type == "NBT"
    assert status.software_version == "11/2013.02"

    assert len(get_deprecation_warning_count(caplog)) == 0