feat: testy

This commit is contained in:
2026-02-02 20:58:50 -08:00
parent dac875c413
commit c829ec97c4
17 changed files with 1823 additions and 9 deletions
+6 -1
View File
@@ -91,4 +91,9 @@ known-first-party = ["py"]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
line-ending = "auto"
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
+4
View File
@@ -1,5 +1,9 @@
# Development dependencies
ruff==0.14.14
pytest==8.3.5
pytest-mock==3.14.0
responses==0.25.3
pytest-asyncio==0.24.0
# Runtime dependencies
requests==2.32.3
+1
View File
@@ -0,0 +1 @@
# Test package for ephemere Python scripts
+207
View File
@@ -0,0 +1,207 @@
"""Tests for analyse_availability functions.
@copyright NHCarrigan
@license Naomi's Public License
@author Naomi Carrigan
"""
import sys
from pathlib import Path
import pytest
# Add the cohort directory to the path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / "cohort"))
from analyse_availability import (
analyze_applicant_availability,
get_utc_blocks_for_hour,
local_hour_to_utc,
parse_time_slots,
parse_utc_offset,
)
class TestParseUtcOffset:
"""Tests for parse_utc_offset function."""
def test_positive_offset(self):
"""Should parse positive UTC offset."""
assert parse_utc_offset("Europe/London (UTC+0)") == 0
assert parse_utc_offset("Europe/Paris (UTC+1)") == 1
assert parse_utc_offset("Asia/Tokyo (UTC+9)") == 9
def test_negative_offset(self):
"""Should parse negative UTC offset."""
assert parse_utc_offset("America/New_York (UTC-5)") == -5
assert parse_utc_offset("America/Los_Angeles (UTC-8)") == -8
def test_offset_with_minutes(self):
"""Should parse offset with minutes component."""
assert parse_utc_offset("Asia/Kolkata (UTC+5:30)") == 5.5
assert parse_utc_offset("Asia/Kathmandu (UTC+5:45)") == 5.75
def test_negative_offset_with_minutes(self):
"""Should parse negative offset with minutes."""
assert parse_utc_offset("Canada/Newfoundland (UTC-3:30)") == -3.5
def test_no_match_returns_zero(self):
"""Should return 0 when no UTC offset found."""
assert parse_utc_offset("Unknown/Timezone") == 0
assert parse_utc_offset("") == 0
class TestParseTimeSlots:
"""Tests for parse_time_slots function."""
def test_single_slot(self):
"""Should parse a single time slot."""
result = parse_time_slots("17:00-18:00")
assert result == [(17, 18)]
def test_multiple_slots(self):
"""Should parse multiple time slots separated by semicolon."""
result = parse_time_slots("07:00-08:00; 19:00-20:00")
assert result == [(7, 8), (19, 20)]
def test_na_values(self):
"""Should return empty list for N/A values."""
assert parse_time_slots("N/A") == []
assert parse_time_slots("na") == []
assert parse_time_slots("") == []
def test_wider_time_range(self):
"""Should parse wider time ranges."""
result = parse_time_slots("09:00-17:00")
assert result == [(9, 17)]
def test_midnight_crossing_slots(self):
"""Should parse time slots that approach midnight."""
result = parse_time_slots("22:00-23:00")
assert result == [(22, 23)]
class TestLocalHourToUtc:
"""Tests for local_hour_to_utc function."""
def test_positive_offset(self):
"""Should convert local hour with positive UTC offset."""
assert local_hour_to_utc(12, 1) == 11 # Noon in UTC+1 -> 11:00 UTC
assert local_hour_to_utc(0, 9) == 15 # Midnight in UTC+9 -> 15:00 UTC
def test_negative_offset(self):
"""Should convert local hour with negative UTC offset."""
assert local_hour_to_utc(12, -5) == 17 # Noon in UTC-5 -> 17:00 UTC
assert local_hour_to_utc(20, -8) == 4 # 8pm in UTC-8 -> 4:00 UTC (next day)
def test_wrapping_around_midnight(self):
"""Should wrap around correctly at midnight."""
assert local_hour_to_utc(1, 5) == 20 # 1am in UTC+5 -> 20:00 UTC (prev day)
assert local_hour_to_utc(23, -3) == 2 # 11pm in UTC-3 -> 2:00 UTC (next day)
def test_zero_offset(self):
"""Should return same hour for zero offset."""
assert local_hour_to_utc(15, 0) == 15
class TestGetUtcBlocksForHour:
"""Tests for get_utc_blocks_for_hour function."""
def test_mornings_block(self):
"""Should return mornings for 6-11 UTC."""
assert "mornings" in get_utc_blocks_for_hour(6)
assert "mornings" in get_utc_blocks_for_hour(9)
assert "mornings" in get_utc_blocks_for_hour(11)
assert "mornings" not in get_utc_blocks_for_hour(12)
def test_afternoons_block(self):
"""Should return afternoons for 12-17 UTC."""
assert "afternoons" in get_utc_blocks_for_hour(12)
assert "afternoons" in get_utc_blocks_for_hour(15)
assert "afternoons" in get_utc_blocks_for_hour(17)
assert "afternoons" not in get_utc_blocks_for_hour(18)
def test_evenings_block(self):
"""Should return evenings for 18-23 UTC."""
assert "evenings" in get_utc_blocks_for_hour(18)
assert "evenings" in get_utc_blocks_for_hour(21)
assert "evenings" in get_utc_blocks_for_hour(23)
assert "evenings" not in get_utc_blocks_for_hour(0)
def test_nights_block(self):
"""Should return nights for 0-5 UTC."""
assert "nights" in get_utc_blocks_for_hour(0)
assert "nights" in get_utc_blocks_for_hour(3)
assert "nights" in get_utc_blocks_for_hour(5)
assert "nights" not in get_utc_blocks_for_hour(6)
class TestAnalyzeApplicantAvailability:
"""Tests for analyze_applicant_availability function."""
def test_basic_availability(self):
"""Should analyze basic availability correctly."""
day_slots = {
"Monday": [(9, 12)],
"Tuesday": [(9, 12)],
"Wednesday": [(9, 12)],
"Thursday": [],
"Friday": [],
"Saturday": [],
"Sunday": [],
}
result = analyze_applicant_availability("UTC (UTC+0)", day_slots)
assert result["utc_offset"] == 0
assert "mornings" in result["available_blocks"]
def test_no_availability(self):
"""Should return empty blocks when no availability."""
day_slots = {
"Monday": [],
"Tuesday": [],
"Wednesday": [],
"Thursday": [],
"Friday": [],
"Saturday": [],
"Sunday": [],
}
result = analyze_applicant_availability("UTC (UTC+0)", day_slots)
assert result["available_blocks"] == []
assert result["total_unique_utc_hours"] == 0
def test_timezone_conversion(self):
"""Should correctly convert timezones."""
day_slots = {
"Monday": [(17, 20)], # 5pm-8pm local
"Tuesday": [(17, 20)],
"Wednesday": [(17, 20)],
"Thursday": [],
"Friday": [],
"Saturday": [],
"Sunday": [],
}
result = analyze_applicant_availability(
"America/New_York (UTC-5)", day_slots
)
assert result["utc_offset"] == -5
# 5pm-8pm in UTC-5 = 22:00-01:00 UTC -> evenings/nights
assert "evenings" in result["available_blocks"]
def test_block_count_threshold(self):
"""Should only include blocks with 3+ occurrences."""
day_slots = {
"Monday": [(9, 10)], # Only 1 hour
"Tuesday": [(9, 10)], # Only 1 hour
"Wednesday": [],
"Thursday": [],
"Friday": [],
"Saturday": [],
"Sunday": [],
}
result = analyze_applicant_availability("UTC (UTC+0)", day_slots)
# Only 2 hours, threshold is 3
assert "mornings" not in result["available_blocks"]