Files
esphome/tests/integration/test_camera_mock.py

74 lines
2.9 KiB
Python

"""Integration test for the camera API flow using a mock camera platform."""
from __future__ import annotations
import asyncio
from aioesphomeapi import CameraInfo, CameraState, EntityState
import pytest
from .state_utils import require_entity
from .types import APIClientConnectedFactory, RunCompiledFunction
# Must match image_size in fixtures/camera_mock.yaml
IMAGE_SIZE = 4096
STREAM_FRAMES = 3
def _verify_frame(data: bytes) -> int:
"""Verify the deterministic frame pattern and return the frame counter."""
assert len(data) == IMAGE_SIZE, f"expected {IMAGE_SIZE} bytes, got {len(data)}"
counter = data[0]
assert data == bytes((counter + i) & 0xFF for i in range(IMAGE_SIZE)), (
"frame pattern mismatch"
)
return counter
@pytest.mark.asyncio
async def test_camera_mock(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Single-image and stream requests deliver reassembled deterministic frames."""
async with run_compiled(yaml_config), api_client_connected() as client:
entities, _ = await client.list_entities_services()
camera = require_entity(entities, "mock_camera", CameraInfo)
loop = asyncio.get_running_loop()
images: list[bytes] = []
single_image: asyncio.Future[None] = loop.create_future()
stream_done: asyncio.Future[None] = loop.create_future()
def on_state(state: EntityState) -> None:
if not (isinstance(state, CameraState) and state.key == camera.key):
return
images.append(bytes(state.data))
if not single_image.done():
single_image.set_result(None)
elif len(images) >= STREAM_FRAMES and not stream_done.done():
stream_done.set_result(None)
client.subscribe_states(on_state)
# Single image request: one complete frame arrives, reassembled
# from multiple chunks (4096 > 1390 byte packets)
client.request_single_image()
await asyncio.wait_for(single_image, timeout=10)
first_counter = _verify_frame(images[0])
# Stream request: multiple consecutive frames arrive
images.clear()
client.request_image_stream()
await asyncio.wait_for(stream_done, timeout=10)
# Frames are distinct, ordered, and fresh per the mock's counter.
# Not exactly consecutive: the API drops frames by design while the
# previous image is still being sent, so allow small gaps.
counters = [_verify_frame(img) for img in images[:STREAM_FRAMES]]
for prev, cur in zip(counters, counters[1:], strict=False):
assert cur != prev, f"duplicate frames: {counters}"
assert ((cur - prev) & 0xFF) < 16, f"frames out of order: {counters}"
assert counters[0] != first_counter, "stream should produce new frames"