Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 136 additions & 1 deletion tests/test_darksky_api.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Tests for Dark Sky Times calculations."""

from datetime import date, datetime, timezone
from datetime import date, datetime, timedelta, timezone

import pytest

from accessisky.api.darksky import (
DarkSkyClient,
DarkSkyWindow,
TwilightType,
get_dark_sky_window,
get_darkness_duration,
get_twilight_type,
is_astronomical_darkness,
)

Expand All @@ -27,6 +31,14 @@ def test_twilight_descriptions(self):
for twilight in TwilightType:
assert twilight.description

def test_sun_angle_ranges(self):
"""Test sun angle ranges for each twilight type."""
assert TwilightType.DAY.sun_angle_range == (0, 0)
assert TwilightType.CIVIL.sun_angle_range == (0, 6)
assert TwilightType.NAUTICAL.sun_angle_range == (6, 12)
assert TwilightType.ASTRONOMICAL.sun_angle_range == (12, 18)
assert TwilightType.NIGHT.sun_angle_range == (18, 90)


class TestDarkSkyWindow:
"""Tests for DarkSkyWindow dataclass."""
Expand Down Expand Up @@ -56,6 +68,17 @@ def test_str_representation(self):
assert "22:30" in s or "10:30" in s # Depends on formatting
assert "5" in s # Hours

def test_str_no_data(self):
"""Test string representation with no data."""
window = DarkSkyWindow(
date=date(2026, 6, 15),
darkness_begins=None,
darkness_ends=None,
darkness_duration_hours=0.0,
)

assert str(window) == "Dark Sky: No data available"

def test_is_currently_dark(self):
"""Test is_currently_dark method."""
window = DarkSkyWindow(
Expand All @@ -77,6 +100,55 @@ def test_is_currently_dark(self):
after = datetime(2026, 6, 16, 5, 0, tzinfo=timezone.utc)
assert not window.is_currently_dark(after)

def test_time_until_darkness(self):
"""Test time_until_darkness method for all paths."""
window = DarkSkyWindow(
date=date(2026, 6, 15),
darkness_begins=datetime(2026, 6, 15, 22, 0, tzinfo=timezone.utc),
darkness_ends=datetime(2026, 6, 16, 4, 0, tzinfo=timezone.utc),
darkness_duration_hours=6.0,
)

before = datetime(2026, 6, 15, 20, 0, tzinfo=timezone.utc)
assert window.time_until_darkness(before) == timedelta(hours=2)

during = datetime(2026, 6, 15, 22, 0, tzinfo=timezone.utc)
assert window.time_until_darkness(during) == timedelta(0)

no_data = DarkSkyWindow(
date=date(2026, 6, 15),
darkness_begins=None,
darkness_ends=None,
darkness_duration_hours=0.0,
)
assert no_data.time_until_darkness(before) is None

def test_time_remaining(self):
"""Test time_remaining method for all paths."""
window = DarkSkyWindow(
date=date(2026, 6, 15),
darkness_begins=datetime(2026, 6, 15, 22, 0, tzinfo=timezone.utc),
darkness_ends=datetime(2026, 6, 16, 4, 0, tzinfo=timezone.utc),
darkness_duration_hours=6.0,
)

before = datetime(2026, 6, 15, 20, 0, tzinfo=timezone.utc)
assert window.time_remaining(before) == timedelta(hours=6)

during = datetime(2026, 6, 16, 1, 0, tzinfo=timezone.utc)
assert window.time_remaining(during) == timedelta(hours=3)

after = datetime(2026, 6, 16, 5, 0, tzinfo=timezone.utc)
assert window.time_remaining(after) == timedelta(0)

no_data = DarkSkyWindow(
date=date(2026, 6, 15),
darkness_begins=datetime(2026, 6, 15, 22, 0, tzinfo=timezone.utc),
darkness_ends=None,
darkness_duration_hours=0.0,
)
assert no_data.time_remaining(during) is None


class TestGetDarkSkyWindow:
"""Tests for get_dark_sky_window function."""
Expand Down Expand Up @@ -131,6 +203,19 @@ def test_no_darkness_polar_summer(self):
assert window.darkness_duration_hours == 0
assert window.no_darkness_reason is not None

def test_no_twilight_non_polar(self):
"""Test missing twilight data at non-polar latitude."""
window = get_dark_sky_window(
latitude=40.0,
longitude=0,
target_date=date(2026, 6, 21),
astronomical_twilight_end=None,
astronomical_twilight_begin=None,
)

assert window.darkness_duration_hours == 0
assert window.no_darkness_reason == "Twilight data not available"


class TestGetDarknessDuration:
"""Tests for get_darkness_duration function."""
Expand Down Expand Up @@ -186,3 +271,53 @@ def test_no_twilight_data(self):
)

assert is_dark is False # Assume not dark if no data


class TestGetTwilightType:
"""Tests for get_twilight_type function."""

def test_all_twilight_types(self):
"""Test all twilight type return values."""
assert get_twilight_type(1.0) == TwilightType.DAY
assert get_twilight_type(0.0) == TwilightType.DAY
assert get_twilight_type(-3.0) == TwilightType.CIVIL
assert get_twilight_type(-9.0) == TwilightType.NAUTICAL
assert get_twilight_type(-15.0) == TwilightType.ASTRONOMICAL
assert get_twilight_type(-25.0) == TwilightType.NIGHT


class TestDarkSkyClient:
"""Tests for DarkSkyClient async wrapper methods."""

@pytest.mark.asyncio
async def test_get_dark_sky_window(self):
"""Test async wrapper for get_dark_sky_window."""
client = DarkSkyClient()
window = await client.get_dark_sky_window(
latitude=45.0,
longitude=-75.0,
target_date=date(2026, 3, 15),
astronomical_twilight_end=datetime(2026, 3, 15, 20, 30, tzinfo=timezone.utc),
astronomical_twilight_begin=datetime(2026, 3, 16, 5, 30, tzinfo=timezone.utc),
)

assert isinstance(window, DarkSkyWindow)
assert window.darkness_duration_hours > 0

@pytest.mark.asyncio
async def test_is_astronomical_darkness(self):
"""Test async wrapper for is_astronomical_darkness."""
client = DarkSkyClient()
is_dark = await client.is_astronomical_darkness(
check_time=datetime(2026, 3, 16, 1, 0, tzinfo=timezone.utc),
twilight_end=datetime(2026, 3, 15, 20, 0, tzinfo=timezone.utc),
twilight_begin=datetime(2026, 3, 16, 4, 0, tzinfo=timezone.utc),
)

assert is_dark is True

@pytest.mark.asyncio
async def test_close(self):
"""Test async close no-op."""
client = DarkSkyClient()
assert await client.close() is None