Files
psa_car_controller/tests/test_unit.py
T
Martin PauliandFlorian BEZANNIER 7ffdb74714 Use temperature measurement from car if available, otherwise fetch from openweather API
Adjust tests to match code changes

Test

Adjust tests to match code changes

Fix code formatting
2025-11-15 18:56:44 +01:00

362 lines
16 KiB
Python

# flake8: noqa
import json
import os
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch
import reverse_geocode
from dateutil.tz import tzutc
from greenery.lego import parse, charclass
from pytz import UTC
from psa_car_controller import psa
from psa_car_controller.common import utils
from psa_car_controller.common.mylogger import my_logger
from psa_car_controller.common.utils import parse_hour, RateLimitException, rate_limit
from psa_car_controller.psa.otp.otp import load_otp, save_otp
from psa_car_controller.psa.setup.app_decoder import get_content_from_apk, GITHUB_USER, GITHUB_REPO
from psa_car_controller.psa.setup.github import github_file_need_to_be_downloaded
from psa_car_controller.psa.connected_car_api import ApiClient
from psa_car_controller.psacc.application.charge_control import ChargeControls
from psa_car_controller.psacc.application.charging import Charging
from psa_car_controller.psacc.application.ecomix import Ecomix
from psa_car_controller.psacc.application.psa_client import PSAClient
from psa_car_controller.psacc.model.car import Car, Cars
from psa_car_controller.psacc.repository import config_repository
from psa_car_controller.psacc.repository.car_model import CarModelRepository
from psa_car_controller.psacc.repository.config_repository import ConfigRepository
from psa_car_controller.psacc.repository.db import Database
from psa_car_controller.psacc.repository.trips import Trips
from psa_car_controller.psacc.utils.utils import get_temp
from tests.data.car_status import FUEL_CAR_STATUS, ELECTRIC_CAR_STATUS, ELECTRIC_CAR_STATUS_V2
from tests.utils import DATA_DIR, record_position, latitude, longitude, date0, date1, date2, date3, record_charging, \
vehicule_list, get_new_test_db, get_date, date4, compare_dict, duration_min, duration_str
from psa_car_controller.web.figures import get_figures, get_battery_curve_fig, get_altitude_fig
dummy_value = 0
def callback_test():
global dummy_value
dummy_value += 1
class TestUnit(unittest.TestCase):
def __init__(self, methodName='runTest'):
super().__init__(methodName)
self.test_online = os.environ.get("TEST_ONLINE", "0") == "1"
self.vehicule_list = vehicule_list
def test_car(self):
car1 = Car("VRAAAAAAA", "1sdfdksnfk222", "Peugeot", "208", 46, 0)
car2 = Car("VR3UHZKX", "1sdfdksnfk222", "Peugeot")
cars = Cars([car1, car2])
cars.save_cars(name=DATA_DIR + "test_car.json")
Cars.load_cars(name=DATA_DIR + "test_car.json")
def test_otp_config(self):
otp_config = load_otp(filename=DATA_DIR + "otp_test.bin")
assert otp_config is not None
save_otp(otp_config, filename=DATA_DIR + "otp_test2.bin")
def test_mypsacc(self):
if self.test_online:
myp = PSAClient.load_config("./config/config.json")
myp.manager.refresh_token_now()
myp.save_config()
myp.get_vehicles()
car = myp.vehicles_list[0]
myp.abrp.abrp_enable_vin.add(car.vin)
myp.get_vehicle_info(myp.vehicles_list[0].vin)
myp.abrp.call(car, 22.1)
def test_open_weather_api(self):
if self.test_online:
myp = PSAClient.load_config("./config/config.json")
assert isinstance(get_temp(str(latitude), str(longitude), myp.weather_api), float)
def test_car_model(self):
assert CarModelRepository().find_model_by_vin("VR3UHZKXZL").name == "e-208"
assert CarModelRepository().find_model_by_vin("VR3UKZKXZM").name == "e-2008"
assert CarModelRepository().find_model_by_vin("VXKUHZKXZL").name == "corsa-e"
def test_c02_signal_cache(self):
start = datetime.utcnow().replace(tzinfo=UTC) - timedelta(minutes=30)
end = datetime.utcnow().replace(tzinfo=UTC)
Ecomix._cache = {'FR': [[start - timedelta(days=1), 100],
[start + timedelta(minutes=1), 10],
[start + timedelta(minutes=2), 20],
[start + timedelta(minutes=3), 30]]}
assert Ecomix.get_co2_from_signal_cache(start, end, "FR") == 20
Ecomix.clean_cache()
self.assertEqual(3, len(Ecomix._cache["FR"]))
def test_c02_signal(self):
if self.test_online:
PSAClient.load_config("./config/config.json")
def_country = "FR"
Ecomix.get_data_from_co2_signal(latitude, longitude, def_country)
res = Ecomix.get_co2_from_signal_cache(datetime.utcnow().replace(tzinfo=UTC) - timedelta(minutes=5),
datetime.utcnow().replace(tzinfo=UTC), def_country)
self.assertIsInstance(res, (int, float))
def test_charge_control(self):
charge_control = ChargeControls()
charge_control.file_name = "test_charge_control.json"
charge_control.save_config(force=True)
def test_battery_curve(self):
vin = self.vehicule_list[0].vin
get_new_test_db()
record_charging()
charge = Database.get_last_charge(vin)
self.assertEqual(charge.stop_at, date4)
res = Database.get_battery_curve(Database.get_db(), date0, date4, vin)
self.assertEqual(4, len(res))
def test_set_charge_price(self):
vin = self.vehicule_list[0].vin
get_new_test_db()
record_charging()
charge = Database.get_last_charge(vin)
charge.price = 42
conn = Database.get_db()
Database.set_chargings_price(conn, charge)
conn.close()
res = Database.get_charge(vin, charge.start_at)
self.assertEqual(charge.price, res.price)
def test_sdk(self):
api = ApiClient()
status: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(FUEL_CAR_STATUS, "Status")
geocode_res = reverse_geocode.search([(status.last_position.geometry.coordinates[:2])[::-1]])[0]
get_new_test_db()
car = Car("XX", "vid", "Peugeot")
car.status = status
myp = PSAClient.load_config(DATA_DIR + "config.json")
myp.record_info(car)
assert geocode_res["country_code"] == "DE"
assert "features" in json.loads(Database.get_recorded_position())
# electric should be first
assert car.status.energy[0].type == 'Electric'
@patch("psa_car_controller.psacc.repository.db.Database.record_position")
def test_electric_record_info(self, mock_db):
api = ApiClient()
status: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(ELECTRIC_CAR_STATUS, "Status")
get_new_test_db()
car = self.vehicule_list[0]
car.status = status
myp = PSAClient.load_config(DATA_DIR + "config.json")
myp.record_info(car)
db_record_position_arg = mock_db.call_args_list[0][0]
expected_result = (None, 'VR3UHZKX', 3196.5, 47.274, -1.59008, 30,
datetime(2022, 3, 26, 11, 2, 54, tzinfo=tzutc()),
59.0,
None,
True,
None)
self.assertEqual(db_record_position_arg, expected_result)
@patch("psa_car_controller.psacc.repository.db.Database.record_battery_soh")
@patch("psa_car_controller.psacc.repository.db.Database.record_position")
def test_electric_record_info_v2(self, mock_db, moock_soh):
api = ApiClient()
status: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(
ELECTRIC_CAR_STATUS_V2, "Status")
get_new_test_db()
car = self.vehicule_list[0]
car.status = status
myp = PSAClient.load_config(DATA_DIR + "config.json")
myp.record_info(car)
db_record_position_arg = mock_db.call_args_list[0][0]
expected_result = (None, 'VR3UHZKX', 3196.5, 47.274, -1.59008, 30,
datetime(2022, 3, 26, 11, 2, 54, tzinfo=tzutc()),
59.0,
None,
True,
None)
self.assertEqual(db_record_position_arg, expected_result)
self.assertEqual(
('VR3UHZKX',
datetime(
2022,
3,
26,
11,
2,
54,
tzinfo=tzutc()),
90),
moock_soh.call_args_list[0][0])
def test_record_position_charging(self):
get_new_test_db()
config_repository.CONFIG_FILENAME = DATA_DIR + "config.ini"
car = self.vehicule_list[0]
record_position()
utils.get_positions = MagicMock(
return_value=[{'dataset': 'srtm30m', 'elevation': 40.0, 'location': {'lat': 47.2183, 'lng': -1.60362}}])
Database.add_altitude_to_db(Database.get_db())
data = json.loads(Database.get_recorded_position())
assert data["features"][1]["geometry"]["coordinates"] == [float(longitude), float(latitude)]
trips = Trips.get_trips(self.vehicule_list)[car.vin]
trip = trips[0]
map(trip.add_temperature, [10, 13, 15])
res = trip.get_info()
assert compare_dict(res, {'consumption_km': 24.21052631578947,
'start_at': date0,
'consumption_by_temp': None,
'positions': {'lat': [latitude], 'long': [longitude]},
'duration': 40.0, 'speed_average': 28.5, 'distance': 19.0, 'mileage': 30.0,
'altitude_diff': 2, 'id': 1, 'consumption': 4.6})
Charging.elec_price = ConfigRepository.read_config(DATA_DIR + "config.ini").Electricity_config
start_level = 40
end_level = 85
mileage = 123456789.1
Charging.record_charging(
car,
"InProgress",
date0,
start_level,
latitude,
longitude,
None,
"slow",
20,
60,
mileage)
Charging.record_charging(car, "InProgress", date1, 70, latitude, longitude, "FR", "slow", 20, 60, mileage)
Charging.record_charging(car, "InProgress", date1, 70, latitude, longitude, "FR", "slow", 20, 60, mileage)
Charging.record_charging(car, "InProgress", date2, 80, latitude, longitude, "FR", "slow", 20, 60, mileage)
Charging.record_charging(car, "Stopped", date3, end_level, latitude, longitude, "FR", "slow", 20, 60, mileage)
chargings = Charging.get_chargings()
co2 = chargings[0]["co2"]
assert isinstance(co2, float)
assert compare_dict(chargings, [{'start_at': date0,
'stop_at': date3,
'duration_str': duration_str,
'duration_min': duration_min,
'VIN': 'VR3UHZKX',
'start_level': 40,
'end_level': 85,
'co2': co2,
'kw': 20.7,
'price': 4.29,
'charging_mode': 'slow',
'mileage': 123456789.1}])
assert get_figures(car)
row = {"start_at": date0.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
"stop_at": date3.strftime('%Y-%m-%dT%H:%M:%S.000Z'), "start_level": start_level, "end_level": end_level}
battery_curve_fix = get_battery_curve_fig(row, car)
assert battery_curve_fix is not None
assert get_altitude_fig(trip) is not None
def test_fuel_car(self):
get_new_test_db()
config_repository.CONFIG_FILENAME = DATA_DIR + "config.ini"
car = self.vehicule_list[1]
Database.record_position(None, car.vin, 11, latitude, longitude, 22, date0, 40, 30, False, None)
Database.record_position(None, car.vin, 20, latitude, longitude, 22, date1, 35, 29, False, None)
Database.record_position(None, car.vin, 30, latitude, longitude, 22, date2, 30, 28, False, None)
trips = Trips.get_trips(self.vehicule_list)
res = trips[car.vin].get_trips_as_dict()
assert compare_dict(res, [{'consumption_km': 6.947368421052632,
'start_at': date0,
'consumption_by_temp': None,
'positions': {'lat': [latitude],
'long': [longitude]},
'duration': 40.0,
'speed_average': 28.5,
'distance': 19.0,
'mileage': 30.0,
'altitude_diff': 0,
'id': 1,
'consumption': 1.32,
'consumption_fuel_km': 4.53}])
def test_none_mileage(self):
get_new_test_db()
config_repository.CONFIG_FILENAME = DATA_DIR + "config.ini"
car = self.vehicule_list[1]
Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(1), 40, 30, False, None)
Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(2), 35, 29, False, None)
Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(3), 30, 28, False, None)
start = get_date(4)
end = get_date(6)
Database.record_position(None, car.vin, 11, latitude, longitude, 22, start, 40, 30, False, None)
Database.record_position(None, car.vin, 20, latitude, longitude, 22, get_date(5), 35, 29, False, None)
Database.record_position(None, car.vin, 30, latitude, longitude, 22, end, 30, 28, False, None)
trips = Trips.get_trips(self.vehicule_list)
res = trips[car.vin].get_trips_as_dict()
assert compare_dict(res, [{'consumption_km': 6.947368421052632,
'start_at': start,
'consumption_by_temp': None,
'positions': {'lat': [latitude],
'long': [longitude]},
'duration': 120.0,
'speed_average': 9.5,
'distance': 19.0,
'mileage': 30.0,
'altitude_diff': 0,
'id': 1,
'consumption': 1.32,
'consumption_fuel_km': 4.53}])
def test_db_callback(self):
old_dummy_value = dummy_value
get_new_test_db()
Database.set_db_callback(callback_test)
assert old_dummy_value == dummy_value
Database.record_position(None, "xx", 11, latitude, longitude - 0.05, None, date0, 40, None, False, None)
assert old_dummy_value != dummy_value
def test_parse_hour(self):
expected_res = [[2, 0, 0], [3, 14, 0], [0, 0, 2], [0, 30, 0]]
assert expected_res == [parse_hour(h) for h in ["PT2H", "PT3H14", "PT2S", "PT30M"]]
def test_rate_limit(self):
@rate_limit(2, 10)
def test_fct():
pass
test_fct()
test_fct()
try:
test_fct()
raise Exception("It should have raise RateLimitException")
except RateLimitException:
pass
def test_parse_apk(self):
filename = "mypeugeot.apk"
try:
os.remove(filename)
except FileNotFoundError:
pass
assert get_content_from_apk(filename, "FR")
assert github_file_need_to_be_downloaded(GITHUB_USER, GITHUB_REPO, "", filename + ".bz2") is False
def test_file_need_to_be_updated(self):
filename = "mypeugeot.apk.bz2"
with open(filename, "w") as f:
f.write(" ")
assert github_file_need_to_be_downloaded(GITHUB_USER, GITHUB_REPO, "", filename) is True
def test_car_model_duplication(self):
car_models = CarModelRepository().models
vins = []
for model in car_models:
self.assertFalse(model.reg in vins, f"duplicate vin prefix {model.reg}")
for vin in vins:
self.assertFalse(vin in model.reg, f"duplicate vin with shorter prefix {vin}")
vins.append(model.reg)
if __name__ == '__main__':
my_logger(handler_level=os.environ.get("DEBUG_LEVEL", 20))
unittest.main()