Exemple #1
0
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"
Exemple #2
0
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]
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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))
Exemple #7
0
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
Exemple #8
0
    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
Exemple #9
0
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)
Exemple #10
0
async def protect_client():
    client = ProtectApiClient("127.0.0.1", 0, "username", "password")
    yield await setup_client(client, SimpleMockWebsocket())
    await cleanup_client(client)
Exemple #11
0
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