Add tests for new SVG camera and remove old ones

This commit is contained in:
Jules 2024-01-02 21:45:37 +01:00
parent c9ec30b8b2
commit b7d39bf753
Signed by: jdejaegh
GPG key ID: 99D6D184CA66933A
5 changed files with 279 additions and 76 deletions

View file

@ -54,7 +54,6 @@ class IrmKmiRadar(CoordinatorEntity, Camera):
width: int | None = None, width: int | None = None,
height: int | None = None) -> bytes | None: height: int | None = None) -> bytes | None:
"""Return still image to be used as thumbnail.""" """Return still image to be used as thumbnail."""
# TODO make it a still image to avoid cuts in playback on the dashboard
return self.coordinator.data.get('animation', {}).get('svg_still') return self.coordinator.data.get('animation', {}).get('svg_still')
async def async_camera_image( async def async_camera_image(

View file

@ -24,7 +24,6 @@ class CurrentWeatherData(TypedDict, total=False):
pressure: float | None pressure: float | None
# TODO cleanup useless fields
class AnimationFrameData(TypedDict, total=False): class AnimationFrameData(TypedDict, total=False):
"""Holds one single frame of the radar camera, along with the timestamp of the frame""" """Holds one single frame of the radar camera, along with the timestamp of the frame"""
time: datetime | None time: datetime | None

View file

@ -83,8 +83,6 @@ class RainGraph:
self.draw_location() self.draw_location()
self._dwg_still = self._dwg self._dwg_still = self._dwg
self._dwg_animated.saveas("animated_rain.svg")
def draw_svg_frame(self): def draw_svg_frame(self):
"""Create the global area to draw the other items""" """Create the global area to draw the other items"""
self._dwg.embed_font(name="Roboto Medium", filename='custom_components/irm_kmi/resources/roboto_medium.ttf') self._dwg.embed_font(name="Roboto Medium", filename='custom_components/irm_kmi/resources/roboto_medium.ttf')
@ -330,4 +328,4 @@ class RainGraph:
self._dwg.add(image) self._dwg.add(image)
def get_dwg(self): def get_dwg(self):
return copy.deepcopy(self._dwg) return copy.deepcopy(self._dwg)

View file

@ -107,74 +107,3 @@ def test_hourly_forecast() -> None:
) )
assert result[8] == expected assert result[8] == expected
@freeze_time(datetime.fromisoformat("2023-12-28T15:30:00+01:00"))
@pytest.mark.skip(reason="Outdated test, cannot be tested this way with the new camera features")
async def test_get_image_nl(
hass: HomeAssistant,
mock_image_irm_kmi_api: AsyncMock,
mock_config_entry: MockConfigEntry) -> None:
api_data = get_api_data("forecast_nl.json")
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
result = await coordinator._async_animation_data(api_data)
# Construct the expected image for the most recent one
tz = pytz.timezone(hass.config.time_zone)
background = Image.open("custom_components/irm_kmi/resources/nl.png").convert('RGBA')
layer = Image.open("tests/fixtures/clouds_nl.png").convert('RGBA')
localisation = Image.open("tests/fixtures/loc_layer_nl.png").convert('RGBA')
temp = Image.alpha_composite(background, layer)
expected = Image.alpha_composite(temp, localisation)
draw = ImageDraw.Draw(expected)
font = ImageFont.truetype("custom_components/irm_kmi/resources/roboto_medium.ttf", 16)
time_image = (datetime.fromisoformat("2023-12-28T14:25:00+00:00")
.astimezone(tz=tz))
time_str = time_image.isoformat(sep=' ', timespec='minutes')
draw.text((4, 4), time_str, (0, 0, 0), font=font)
result_image = Image.open(BytesIO(result['sequence'][-1]['image'])).convert('RGBA')
assert list(result_image.getdata()) == list(expected.getdata())
most_recent_image = result['sequence'][result['most_recent_image_idx']]['image']
thumb_image = Image.open(BytesIO(most_recent_image)).convert('RGBA')
assert list(thumb_image.getdata()) == list(expected.getdata())
assert result['hint'] == "No rain forecasted shortly"
@freeze_time(datetime.fromisoformat("2023-12-26T18:31:00+01:00"))
@pytest.mark.skip(reason="Outdated test, cannot be tested this way with the new camera features")
async def test_get_image_be(
hass: HomeAssistant,
mock_image_irm_kmi_api: AsyncMock,
mock_config_entry: MockConfigEntry
) -> None:
api_data = get_api_data("forecast.json")
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
result = await coordinator._async_animation_data(api_data)
# Construct the expected image for the most recent one
tz = pytz.timezone(hass.config.time_zone)
background = Image.open("custom_components/irm_kmi/resources/be_black.png").convert('RGBA')
layer = Image.open("tests/fixtures/clouds_be.png").convert('RGBA')
localisation = Image.open("tests/fixtures/loc_layer_be_n.png").convert('RGBA')
temp = Image.alpha_composite(background, layer)
expected = Image.alpha_composite(temp, localisation)
draw = ImageDraw.Draw(expected)
font = ImageFont.truetype("custom_components/irm_kmi/resources/roboto_medium.ttf", 16)
time_image = (datetime.fromisoformat("2023-12-26T18:30:00+01:00")
.astimezone(tz=tz))
time_str = time_image.isoformat(sep=' ', timespec='minutes')
draw.text((4, 4), time_str, (255, 255, 255), font=font)
result_image = Image.open(BytesIO(result['sequence'][9]['image'])).convert('RGBA')
assert list(result_image.getdata()) == list(expected.getdata())
most_recent_image = result['sequence'][result['most_recent_image_idx']]['image']
thumb_image = Image.open(BytesIO(most_recent_image)).convert('RGBA')
assert list(thumb_image.getdata()) == list(expected.getdata())
assert result['hint'] == "No rain forecasted shortly"

278
tests/test_rain_graph.py Normal file
View file

@ -0,0 +1,278 @@
import base64
from datetime import datetime, timedelta
from custom_components.irm_kmi.data import (AnimationFrameData,
RadarAnimationData)
from custom_components.irm_kmi.rain_graph import RainGraph
def get_radar_animation_data() -> RadarAnimationData:
with open("tests/fixtures/clouds_be.png", "rb") as file:
image_data = file.read()
with open("tests/fixtures/loc_layer_be_n.png", "rb") as file:
location = file.read()
sequence = [
AnimationFrameData(
time=datetime.fromisoformat("2023-12-26T18:30:00+00:00") + timedelta(minutes=10 * i),
image=image_data,
value=2,
position=.5,
position_lower=.4,
position_higher=.6
)
for i in range(10)
]
return RadarAnimationData(
sequence=sequence,
most_recent_image_idx=2,
hint="Testing SVG camera",
unit="mm/10min",
location=location
)
def test_svg_frame_setup():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_svg_frame()
svg_str = rain_graph.get_dwg().tostring()
with open("custom_components/irm_kmi/resources/roboto_medium.ttf", "rb") as file:
font_b64 = base64.b64encode(file.read()).decode('utf-8')
assert '#385E95' in svg_str
assert 'font-family: "Roboto Medium";' in svg_str
assert font_b64 in svg_str
def test_svg_hint():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.write_hint()
svg_str = rain_graph.get_dwg().tostring()
assert "Testing SVG camera" in svg_str
def test_svg_time_bars():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_hour_bars()
svg_str = rain_graph.get_dwg().tostring()
assert "19h" in svg_str
assert "20h" in svg_str
assert "<line" in svg_str
assert 'stroke="white"' in svg_str
def test_draw_chances_path():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_chances_path()
svg_str = rain_graph.get_dwg().tostring()
assert 'fill="#63c8fa"' in svg_str
assert 'opacity="0.3"' in svg_str
assert 'stroke="none"' in svg_str
assert '<path ' in svg_str
def test_draw_data_line():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_data_line()
svg_str = rain_graph.get_dwg().tostring()
assert 'fill="none"' in svg_str
assert 'stroke-width="2"' in svg_str
assert 'stroke="#63c8fa"' in svg_str
assert '<path ' in svg_str
def test_insert_background():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.insert_background()
with open("custom_components/irm_kmi/resources/be_white.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')
svg_str = rain_graph.get_dwg().tostring()
assert png_b64 in svg_str
assert "<image " in svg_str
assert 'height="490"' in svg_str
assert 'width="640"' in svg_str
assert 'x="0"' in svg_str
assert 'y="0"' in svg_str
def test_draw_current_frame_line_moving():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_current_fame_line()
str_svg = rain_graph.get_dwg().tostring()
assert '<line' in str_svg
assert 'id="now"' in str_svg
assert 'opacity="1"' in str_svg
assert 'stroke="white"' in str_svg
assert 'stroke-width="2"' in str_svg
assert 'x1="50' in str_svg
assert 'x2="50' in str_svg
assert 'y1="520' in str_svg
assert 'y2="670' in str_svg
assert 'animateTransform' in str_svg
assert 'attributeName="transform"' in str_svg
assert 'repeatCount="indefinite"' in str_svg
assert 'type="translate"' in str_svg
def test_draw_current_frame_line_index():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_current_fame_line(0)
str_svg = rain_graph.get_dwg().tostring()
assert '<line' in str_svg
assert 'id="now"' in str_svg
assert 'opacity="1"' in str_svg
assert 'stroke="white"' in str_svg
assert 'stroke-width="2"' in str_svg
assert 'x1="50' in str_svg
assert 'x2="50' in str_svg
assert 'y1="520' in str_svg
assert 'y2="670' in str_svg
assert 'animateTransform' not in str_svg
assert 'attributeName="transform"' not in str_svg
assert 'repeatCount="indefinite"' not in str_svg
assert 'type="translate"' not in str_svg
def test_draw_description_text():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_description_text()
str_svg = rain_graph.get_dwg().tostring()
assert "18:30" in str_svg
assert "18:40" in str_svg
assert "18:50" in str_svg
assert "19:00" in str_svg
assert "19:10" in str_svg
assert "19:20" in str_svg
assert "19:30" in str_svg
assert "19:40" in str_svg
assert "19:50" in str_svg
assert "20:00" in str_svg
assert str_svg.count("2mm/10") == 10
assert 'class="roboto"' in str_svg
def test_draw_cloud_layer():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.insert_cloud_layer()
str_svg = rain_graph.get_dwg().tostring()
with open("tests/fixtures/clouds_be.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')
assert str_svg.count(png_b64) == 10
assert str_svg.count('height="490"') == 10
assert str_svg.count('width="640"') == 11 # Is also the width of the SVG itself
def test_draw_location_layer():
data = get_radar_animation_data()
rain_graph = RainGraph(
animation_data=data,
background_image_path="custom_components/irm_kmi/resources/be_white.png",
background_size=(640, 490),
auto=False
)
rain_graph.draw_location()
str_svg = rain_graph.get_dwg().tostring()
with open("tests/fixtures/loc_layer_be_n.png", "rb") as file:
png_b64 = base64.b64encode(file.read()).decode('utf-8')
assert png_b64 in str_svg