"""Define fixtures for Volvo unit tests.""" from collections.abc import AsyncGenerator, Awaitable, Callable, Generator from dataclasses import dataclass from unittest.mock import AsyncMock, patch import pytest from volvocarsapi.api import VolvoCarsApi from volvocarsapi.auth import TOKEN_URL from volvocarsapi.models import ( VolvoCarsAvailableCommand, VolvoCarsCommandResult, VolvoCarsLocation, VolvoCarsValueField, VolvoCarsValueStatusField, VolvoCarsVehicle, ) from homeassistant.components.application_credentials import ( ClientCredential, async_import_client_credential, ) from homeassistant.components.volvo.api import VolvoAuth from homeassistant.components.volvo.const import CONF_VIN, DOMAIN from homeassistant.const import CONF_API_KEY, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.helpers.config_entry_oauth2_flow import ( OAuth2Session, async_get_config_entry_implementation, ) from homeassistant.setup import async_setup_component from homeassistant.util.json import JsonObjectType from . import async_load_fixture_as_json, async_load_fixture_as_value_field from .const import ( CLIENT_ID, CLIENT_SECRET, DEFAULT_API_KEY, DEFAULT_MODEL, DEFAULT_VIN, MOCK_ACCESS_TOKEN, SERVER_TOKEN_RESPONSE, ) from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker @dataclass class MockApiData: """Container for mock API data.""" vehicle: VolvoCarsVehicle command_result: VolvoCarsCommandResult commands: list[VolvoCarsAvailableCommand] location: dict[str, VolvoCarsLocation] availability: dict[str, VolvoCarsValueField] brakes: dict[str, VolvoCarsValueField] diagnostics: dict[str, VolvoCarsValueField] doors: dict[str, VolvoCarsValueField] energy_capabilities: JsonObjectType energy_state: dict[str, VolvoCarsValueStatusField] engine_status: dict[str, VolvoCarsValueField] engine_warnings: dict[str, VolvoCarsValueField] fuel_status: dict[str, VolvoCarsValueField] odometer: dict[str, VolvoCarsValueField] recharge_status: dict[str, VolvoCarsValueField] statistics: dict[str, VolvoCarsValueField] tyres: dict[str, VolvoCarsValueField] warnings: dict[str, VolvoCarsValueField] windows: dict[str, VolvoCarsValueField] @pytest.fixture(params=[DEFAULT_MODEL]) def full_model(request: pytest.FixtureRequest) -> str: """Define which model to use when running the test. Use as a decorator.""" return request.param @pytest.fixture def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: """Return the default mocked config entry.""" config_entry = MockConfigEntry( domain=DOMAIN, unique_id=DEFAULT_VIN, data={ "auth_implementation": DOMAIN, CONF_API_KEY: DEFAULT_API_KEY, CONF_VIN: DEFAULT_VIN, CONF_TOKEN: { "access_token": MOCK_ACCESS_TOKEN, "refresh_token": "mock-refresh-token", "expires_at": 123456789, }, }, ) config_entry.add_to_hass(hass) return config_entry @pytest.fixture async def mock_api( hass: HomeAssistant, full_model: str, mock_config_entry: MockConfigEntry, aioclient_mock: AiohttpClientMocker, setup_credentials, ) -> AsyncGenerator[VolvoCarsApi]: """Mock the Volvo API.""" mock_api_data = await _async_load_mock_api_data(hass, full_model) implementation = await async_get_config_entry_implementation( hass, mock_config_entry ) oauth_session = OAuth2Session(hass, mock_config_entry, implementation) auth = VolvoAuth(aioclient_mock, oauth_session) api = VolvoCarsApi( aioclient_mock, auth, mock_config_entry.data[CONF_API_KEY], mock_config_entry.data[CONF_VIN], ) with patch( "homeassistant.components.volvo.VolvoCarsApi", return_value=api, ): api.async_execute_command = AsyncMock(return_value=mock_api_data.command_result) api.async_get_brakes_status = AsyncMock(return_value=mock_api_data.brakes) api.async_get_command_accessibility = AsyncMock( return_value=mock_api_data.availability ) api.async_get_commands = AsyncMock(return_value=mock_api_data.commands) api.async_get_diagnostics = AsyncMock(return_value=mock_api_data.diagnostics) api.async_get_doors_status = AsyncMock(return_value=mock_api_data.doors) api.async_get_energy_capabilities = AsyncMock( return_value=mock_api_data.energy_capabilities ) api.async_get_energy_state = AsyncMock(return_value=mock_api_data.energy_state) api.async_get_engine_status = AsyncMock( return_value=mock_api_data.engine_status ) api.async_get_engine_warnings = AsyncMock( return_value=mock_api_data.engine_warnings ) api.async_get_fuel_status = AsyncMock(return_value=mock_api_data.fuel_status) api.async_get_location = AsyncMock(return_value=mock_api_data.location) api.async_get_odometer = AsyncMock(return_value=mock_api_data.odometer) api.async_get_recharge_status = AsyncMock( return_value=mock_api_data.recharge_status ) api.async_get_statistics = AsyncMock(return_value=mock_api_data.statistics) api.async_get_tyre_states = AsyncMock(return_value=mock_api_data.tyres) api.async_get_vehicle_details = AsyncMock(return_value=mock_api_data.vehicle) api.async_get_warnings = AsyncMock(return_value=mock_api_data.warnings) api.async_get_window_states = AsyncMock(return_value=mock_api_data.windows) yield api @pytest.fixture(autouse=True) async def setup_credentials(hass: HomeAssistant) -> None: """Fixture to setup credentials.""" assert await async_setup_component(hass, "application_credentials", {}) await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), ) @pytest.fixture async def setup_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, aioclient_mock: AiohttpClientMocker, ) -> Callable[[], Awaitable[bool]]: """Fixture to set up the integration.""" async def run() -> bool: aioclient_mock.post( TOKEN_URL, json=SERVER_TOKEN_RESPONSE, ) result = await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() return result return run @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: """Mock setting up a config entry.""" with patch( "homeassistant.components.volvo.async_setup_entry", return_value=True ) as mock_setup: yield mock_setup async def _async_load_mock_api_data( hass: HomeAssistant, full_model: str ) -> MockApiData: """Load all mock API data from fixtures.""" vehicle_data = await async_load_fixture_as_json(hass, "vehicle", full_model) vehicle = VolvoCarsVehicle.from_dict(vehicle_data) command_result = VolvoCarsCommandResult(DEFAULT_VIN, "COMPLETED", message="") commands_data = ( await async_load_fixture_as_json(hass, "commands", full_model) ).get("data") commands = [VolvoCarsAvailableCommand.from_dict(item) for item in commands_data] location_data = await async_load_fixture_as_json(hass, "location", full_model) location = {"location": VolvoCarsLocation.from_dict(location_data)} availability = await async_load_fixture_as_value_field( hass, "availability", full_model ) brakes = await async_load_fixture_as_value_field(hass, "brakes", full_model) diagnostics = await async_load_fixture_as_value_field( hass, "diagnostics", full_model ) doors = await async_load_fixture_as_value_field(hass, "doors", full_model) energy_capabilities = await async_load_fixture_as_json( hass, "energy_capabilities", full_model ) energy_state_data = await async_load_fixture_as_json( hass, "energy_state", full_model ) energy_state = { key: VolvoCarsValueStatusField.from_dict(value) for key, value in energy_state_data.items() } engine_status = await async_load_fixture_as_value_field( hass, "engine_status", full_model ) engine_warnings = await async_load_fixture_as_value_field( hass, "engine_warnings", full_model ) fuel_status = await async_load_fixture_as_value_field( hass, "fuel_status", full_model ) odometer = await async_load_fixture_as_value_field(hass, "odometer", full_model) recharge_status = await async_load_fixture_as_value_field( hass, "recharge_status", full_model ) statistics = await async_load_fixture_as_value_field(hass, "statistics", full_model) tyres = await async_load_fixture_as_value_field(hass, "tyres", full_model) warnings = await async_load_fixture_as_value_field(hass, "warnings", full_model) windows = await async_load_fixture_as_value_field(hass, "windows", full_model) return MockApiData( vehicle=vehicle, command_result=command_result, commands=commands, location=location, availability=availability, brakes=brakes, diagnostics=diagnostics, doors=doors, energy_capabilities=energy_capabilities, energy_state=energy_state, engine_status=engine_status, engine_warnings=engine_warnings, fuel_status=fuel_status, odometer=odometer, recharge_status=recharge_status, statistics=statistics, tyres=tyres, warnings=warnings, windows=windows, )