generated from nhcarrigan/template
feat: testy
This commit is contained in:
@@ -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_*"]
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Test package for ephemere Python scripts
|
||||
@@ -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"]
|
||||
Reference in New Issue
Block a user