async def test_check_ws_no_ws(protect_client: ProtectApiClient, caplog: pytest.LogCaptureFixture): caplog.set_level(logging.DEBUG) await protect_client.async_disconnect_ws() protect_client._last_ws_status = False active_ws = protect_client.check_ws() assert active_ws is False expected_logs = ["Disconnecting websocket...", "Websocket connection not active, failing back to polling"] assert expected_logs == [rec.message for rec in caplog.records] assert caplog.records[1].levelname == "DEBUG"
async def test_check_ws_connected(protect_client_ws: ProtectApiClient, caplog: pytest.LogCaptureFixture): caplog.set_level(logging.DEBUG) active_ws = protect_client_ws.check_ws() assert active_ws is True expected_logs: List[str] = [] assert expected_logs == [rec.message for rec in caplog.records]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the UniFi Protect config entries.""" async_start_discovery(hass) session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)) protect = ProtectApiClient( host=entry.data[CONF_HOST], port=entry.data[CONF_PORT], username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], verify_ssl=entry.data[CONF_VERIFY_SSL], session=session, subscribed_models=DEVICES_FOR_SUBSCRIBE, override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False), ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False), ignore_unadopted=False, ) _LOGGER.debug("Connect to UniFi Protect") data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry) try: nvr_info = await protect.get_nvr() except NotAuthorized as err: raise ConfigEntryAuthFailed(err) from err except (asyncio.TimeoutError, ClientError, ServerDisconnectedError) as err: raise ConfigEntryNotReady from err if nvr_info.version < MIN_REQUIRED_PROTECT_V: _LOGGER.error( OUTDATED_LOG_MESSAGE, nvr_info.version, MIN_REQUIRED_PROTECT_V, ) return False await async_migrate_data(hass, entry, protect) if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac) await data_service.async_setup() if not data_service.last_update_success: raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) async_setup_services(hass) hass.http.register_view(ThumbnailProxyView(hass)) hass.http.register_view(VideoProxyView(hass)) entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)) return True
async def setup_client(client: ProtectApiClient, websocket: SimpleMockWebsocket, timeout: int = 0): mock_cs = Mock() mock_session = AsyncMock() mock_session.ws_connect = AsyncMock(return_value=websocket) mock_cs.return_value = mock_session ws = await client.get_websocket() ws.timeout_interval = timeout ws._get_session = mock_cs # type: ignore client.api_request = AsyncMock( side_effect=mock_api_request) # type: ignore client.api_request_raw = AsyncMock( side_effect=mock_api_request_raw) # type: ignore client.ensure_authenticated = AsyncMock() # type: ignore await client.update() return client
def test_bootstrap_device_not_adopted_enabled(bootstrap, protect_client: ProtectApiClient): bootstrap["cameras"][0]["isAdopted"] = False protect_client.ignore_unadopted = False obj: Bootstrap = Bootstrap.from_unifi_dict(**deepcopy(bootstrap), api=protect_client) set_no_debug() obj_construct: Bootstrap = Bootstrap.from_unifi_dict(**deepcopy(bootstrap), api=protect_client) set_debug() assert len(obj.cameras) == len(bootstrap["cameras"]) assert obj.cameras == obj_construct.cameras
async def test_process_events_motion(protect_client: ProtectApiClient, now, camera): def get_camera(): return protect_client.bootstrap.cameras[camera["id"]] bootstrap_before = protect_client.bootstrap.unifi_dict() camera_before = get_camera().copy() expected_event_id = "bf9a241afe74821ceffffd05" async def get_events(*args, **kwargs): return [ { "id": expected_event_id, "type": "motion", "start": to_js_time(now - timedelta(seconds=30)), "end": to_js_time(now), "score": 0, "smartDetectTypes": [], "smartDetectEvents": [], "camera": camera["id"], "partition": None, "user": None, "metadata": {}, "thumbnail": f"e-{expected_event_id}", "heatmap": f"e-{expected_event_id}", "modelKey": "event", }, ] setattr(protect_client, "get_events_raw", get_events) protect_client._last_update = NEVER_RAN await protect_client.update() camera_before.is_motion_detected = False bootstrap = protect_client.bootstrap.unifi_dict() camera = get_camera() event = camera.last_motion_event camera.last_motion_event_id = None camera_before.last_motion_event_id = None camera_before.last_ring_event_id = None camera_before.last_smart_detect_event_id = None assert bootstrap == bootstrap_before assert camera.dict() == camera_before.dict() assert event.id == expected_event_id assert event.type == EventType.MOTION assert event.thumbnail_id == f"e-{expected_event_id}" assert event.heatmap_id == f"e-{expected_event_id}" assert event.start == (now - timedelta(seconds=30))
def mock_entry(hass: HomeAssistant, ufp_config_entry: MockConfigEntry, ufp_client: ProtectApiClient): """Mock ProtectApiClient for testing.""" with _patch_discovery(no_device=True), patch( "homeassistant.components.unifiprotect.ProtectApiClient" ) as mock_api: ufp_config_entry.add_to_hass(hass) mock_api.return_value = ufp_client ufp = MockUFPFixture(ufp_config_entry, ufp_client) def subscribe( ws_callback: Callable[[WSSubscriptionMessage], None]) -> Any: ufp.ws_subscription = ws_callback return Mock() ufp_client.subscribe_websocket = subscribe yield ufp
async def _async_get_nvr_data( self, user_input: dict[str, Any], ) -> tuple[NVR | None, dict[str, str]]: session = async_create_clientsession( self.hass, cookie_jar=CookieJar(unsafe=True) ) host = user_input[CONF_HOST] port = user_input.get(CONF_PORT, DEFAULT_PORT) verify_ssl = user_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) protect = ProtectApiClient( session=session, host=host, port=port, username=user_input[CONF_USERNAME], password=user_input[CONF_PASSWORD], verify_ssl=verify_ssl, ) errors = {} nvr_data = None try: nvr_data = await protect.get_nvr() except NotAuthorized as ex: _LOGGER.debug(ex) errors[CONF_PASSWORD] = "invalid_auth" except NvrError as ex: _LOGGER.debug(ex) errors["base"] = "cannot_connect" else: if nvr_data.version < MIN_REQUIRED_PROTECT_V: _LOGGER.debug( OUTDATED_LOG_MESSAGE, nvr_data.version, MIN_REQUIRED_PROTECT_V, ) errors["base"] = "protect_version" return nvr_data, errors
async def protect_client_ws(): set_no_debug() client = ProtectApiClient("127.0.0.1", 0, "username", "password") yield await setup_client(client, MockWebsocket(), timeout=30) await cleanup_client(client)
async def protect_client(): client = ProtectApiClient("127.0.0.1", 0, "username", "password") yield await setup_client(client, SimpleMockWebsocket()) await cleanup_client(client)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the UniFi Protect config entries.""" _async_import_options_from_data_if_missing(hass, entry) session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)) protect = ProtectApiClient( host=entry.data[CONF_HOST], port=entry.data[CONF_PORT], username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], verify_ssl=entry.data[CONF_VERIFY_SSL], session=session, subscribed_models=DEVICES_FOR_SUBSCRIBE, override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False), ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False), ) _LOGGER.debug("Connect to UniFi Protect") data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry) try: nvr_info = await protect.get_nvr() except NotAuthorized as err: raise ConfigEntryAuthFailed(err) from err except (asyncio.TimeoutError, NvrError, ServerDisconnectedError) as notreadyerror: raise ConfigEntryNotReady from notreadyerror if nvr_info.version < MIN_REQUIRED_PROTECT_V: _LOGGER.error( ("You are running v%s of UniFi Protect. Minimum required version is v%s. " "Please upgrade UniFi Protect and then retry"), nvr_info.version, MIN_REQUIRED_PROTECT_V, ) return False await _async_migrate_data(hass, entry, protect) if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac) await data_service.async_setup() if not data_service.last_update_success: raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service platforms = PLATFORMS if above_ha_version(2021, 12): platforms = PLATFORMS_NEXT hass.config_entries.async_setup_platforms(entry, platforms) services = [ ( SERVICE_ADD_DOORBELL_TEXT, functools.partial(add_doorbell_text, hass), DOORBELL_TEXT_SCHEMA, ), ( SERVICE_REMOVE_DOORBELL_TEXT, functools.partial(remove_doorbell_text, hass), DOORBELL_TEXT_SCHEMA, ), ( SERVICE_SET_DEFAULT_DOORBELL_TEXT, functools.partial(set_default_doorbell_text, hass), DOORBELL_TEXT_SCHEMA, ), (SERVICE_PROFILE_WS, functools.partial(profile_ws, hass), PROFILE_WS_SCHEMA), ] for name, method, schema in services: if hass.services.has_service(DOMAIN, name): continue hass.services.async_register(DOMAIN, name, method, schema=schema) hass.http.register_view(ThumbnailProxyView(hass)) entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)) return True