"""Common fixtures for the Growatt server tests.""" from unittest.mock import patch import pytest from homeassistant.components.growatt_server.const import ( AUTH_API_TOKEN, AUTH_PASSWORD, CONF_AUTH_TYPE, CONF_PLANT_ID, DEFAULT_URL, DOMAIN, ) from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_URL, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @pytest.fixture def mock_growatt_v1_api(): """Return a mocked Growatt V1 API. This fixture provides the happy path for integration setup and basic operations. Individual tests can override specific return values to test error conditions. Methods mocked for integration setup: - device_list: Called during async_setup_entry to discover devices - plant_energy_overview: Called by total coordinator during first refresh Methods mocked for MIN device coordinator refresh: - min_detail: Provides device state (e.g., acChargeEnable for switches) - min_settings: Provides settings (e.g. TOU periods) - min_energy: Provides energy data (empty for switch tests, sensors need real data) Methods mocked for switch operations: - min_write_parameter: Called by switch entities to change settings """ with patch("growattServer.OpenApiV1", autospec=True) as mock_v1_api_class: mock_v1_api = mock_v1_api_class.return_value # Called during setup to discover devices mock_v1_api.device_list.return_value = { "devices": [ { "device_sn": "MIN123456", "type": 7, # MIN device type } ] } # Called by MIN device coordinator during refresh mock_v1_api.min_detail.return_value = { "deviceSn": "MIN123456", "acChargeEnable": 1, # AC charge enabled - read by switch entity } # Called by MIN device coordinator during refresh mock_v1_api.min_settings.return_value = { # Forced charge time segments (not used by switch, but coordinator fetches it) "forcedTimeStart1": "06:00", "forcedTimeStop1": "08:00", "forcedChargeBatMode1": 1, "forcedChargeFlag1": 1, "forcedTimeStart2": "22:00", "forcedTimeStop2": "24:00", "forcedChargeBatMode2": 0, "forcedChargeFlag2": 0, } # Called by MIN device coordinator during refresh # Provide realistic energy data for sensor tests mock_v1_api.min_energy.return_value = { "eChargeToday": 5.2, "eChargeTotal": 125.8, "eDischargeToday": 8.1, "eDischargeTotal": 245.6, "eSelfToday": 12.5, "eSelfTotal": 320.4, "eBatChargeToday": 6.3, "eBatChargeTotal": 150.2, "eBatDischargeToday": 7.8, "eBatDischargeTotal": 180.5, } # Called by total coordinator during refresh mock_v1_api.plant_energy_overview.return_value = { "today_energy": 12.5, "total_energy": 1250.0, "current_power": 2500, } # Called by switch entities during turn_on/turn_off mock_v1_api.min_write_parameter.return_value = None yield mock_v1_api @pytest.fixture def mock_growatt_classic_api(): """Return a mocked Growatt Classic API. This fixture provides the happy path for Classic API integration setup. Individual tests can override specific return values to test error conditions. Methods mocked for integration setup: - login: Called during get_device_list_classic to authenticate - plant_list: Called during setup if plant_id is default (to auto-select plant) - device_list: Called during async_setup_entry to discover devices Methods mocked for total coordinator refresh: - plant_info: Provides plant totals (energy, power, money) for Classic API Methods mocked for device-specific tests: - tlx_detail: Provides TLX device data (kept for potential future tests) """ with patch("growattServer.GrowattApi", autospec=True) as mock_classic_api_class: # Use the autospec'd mock instance instead of creating a new Mock() mock_classic_api = mock_classic_api_class.return_value # Called during setup to authenticate with Classic API mock_classic_api.login.return_value = {"success": True, "user": {"id": 12345}} # Called during setup if plant_id is default (auto-select first plant) mock_classic_api.plant_list.return_value = {"data": [{"plantId": "12345"}]} # Called during setup to discover devices mock_classic_api.device_list.return_value = [ {"deviceSn": "MIN123456", "deviceType": "min"} ] # Called by total coordinator during refresh for Classic API mock_classic_api.plant_info.return_value = { "deviceList": [], "totalEnergy": 1250.0, "todayEnergy": 12.5, "invTodayPpv": 2500, "plantMoneyText": "123.45/USD", } # Called for TLX device coordinator (kept for potential future tests) mock_classic_api.tlx_detail.return_value = { "data": { "deviceSn": "TLX123456", } } yield mock_classic_api @pytest.fixture def mock_config_entry() -> MockConfigEntry: """Return the default mocked config entry (V1 API with token auth). This is the primary config entry used by most tests. For Classic API tests, use mock_config_entry_classic instead. """ return MockConfigEntry( domain=DOMAIN, data={ CONF_AUTH_TYPE: AUTH_API_TOKEN, CONF_TOKEN: "test_token_123", CONF_URL: DEFAULT_URL, "user_id": "12345", CONF_PLANT_ID: "plant_123", "name": "Test Plant", }, unique_id="plant_123", ) @pytest.fixture def mock_config_entry_classic() -> MockConfigEntry: """Return a mocked config entry for Classic API (password auth). Use this for tests that specifically need to test Classic API behavior. Most tests use the default mock_config_entry (V1 API) instead. """ return MockConfigEntry( domain=DOMAIN, data={ CONF_AUTH_TYPE: AUTH_PASSWORD, CONF_USERNAME: "test_user", CONF_PASSWORD: "test_password", CONF_URL: DEFAULT_URL, CONF_PLANT_ID: "12345", "name": "Test Plant", }, unique_id="12345", ) @pytest.fixture async def init_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_growatt_v1_api ) -> MockConfigEntry: """Set up the Growatt Server integration for testing (V1 API). This combines mock_config_entry and mock_growatt_v1_api to provide a fully initialized integration ready for testing. Use @pytest.mark.usefixtures("init_integration") to automatically set up the integration before your test runs. For Classic API tests, manually set up using mock_config_entry_classic and mock_growatt_classic_api instead. """ # The mock_growatt_v1_api fixture is required for patches to be active assert mock_growatt_v1_api is not None mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() return mock_config_entry