Skip to content

Commit 1442129

Browse files
committed
test: initial tests for utils
1 parent ac03fcb commit 1442129

File tree

7 files changed

+900
-0
lines changed

7 files changed

+900
-0
lines changed

pixi.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ setup-hooks = "pre-commit install"
1919
test = "pytest tests/ -v"
2020
test-cov = "pytest tests/ -v --cov=utils --cov-report=term-missing"
2121
test-fast = "pytest tests/ -v -x"
22+
pre = "pre-commit run --all-files"
2223

2324
[dependencies]
2425
python = ">=3.12.3,<3.13"

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Tests for Python Deadlines project

tests/conftest.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Pytest configuration and fixtures for Python Deadlines tests."""
2+
3+
import pytest
4+
import yaml
5+
6+
7+
@pytest.fixture()
8+
def sample_conference():
9+
"""Sample valid conference data for testing."""
10+
return {
11+
"conference": "PyCon Test",
12+
"year": 2025,
13+
"link": "https://test.pycon.org",
14+
"cfp": "2025-02-15 23:59:00",
15+
"place": "Test City, Test Country",
16+
"start": "2025-06-01",
17+
"end": "2025-06-03",
18+
"sub": "PY",
19+
"timezone": "America/New_York",
20+
}
21+
22+
23+
@pytest.fixture()
24+
def invalid_conference():
25+
"""Sample invalid conference data for testing validation."""
26+
return {
27+
"conference": "Invalid Conf",
28+
"year": 1988, # Before Python existed
29+
"link": "not-a-url",
30+
"cfp": "invalid-date",
31+
"place": "", # Empty required field
32+
"start": "2025-06-03",
33+
"end": "2025-06-01", # End before start
34+
"sub": "INVALID",
35+
}
36+
37+
38+
@pytest.fixture()
39+
def temp_yaml_file(tmp_path):
40+
"""Create a temporary YAML file for testing."""
41+
42+
def _create_yaml_file(data):
43+
yaml_file = tmp_path / "test_conferences.yml"
44+
with yaml_file.open("w", encoding="utf-8") as f:
45+
yaml.dump(data, f, default_flow_style=False)
46+
return str(yaml_file)
47+
48+
return _create_yaml_file
49+
50+
51+
@pytest.fixture()
52+
def sample_csv_data():
53+
"""Sample CSV data for import testing."""
54+
return """Conference Name,Year,Website,CFP Deadline,Location,Start Date,End Date,Type
55+
PyCon Test,2025,https://test.pycon.org,2025-02-15 23:59:00,"Test City, Test Country",2025-06-01,2025-06-03,PY
56+
Django Test,2025,https://test.django.org,2025-03-01 23:59:00,"Another City, Test Country",2025-07-01,2025-07-03,WEB"""

tests/test_data_processing.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""Tests for data processing and sorting functionality."""
2+
3+
# Add utils to path for imports
4+
import sys
5+
from datetime import datetime
6+
from datetime import timezone
7+
from pathlib import Path
8+
9+
import yaml
10+
11+
sys.path.append(str(Path(__file__).parent.parent / "utils"))
12+
13+
import sort_yaml
14+
from tidy_conf.schema import Conference
15+
16+
17+
class TestDataProcessing:
18+
"""Test data processing functions."""
19+
20+
def test_sort_by_cfp(self, sample_conference):
21+
"""Test sorting conferences by CFP deadline."""
22+
conf = Conference(**sample_conference)
23+
cfp_date = sort_yaml.sort_by_cfp(conf)
24+
25+
assert cfp_date == "2025-02-15 23:59:00"
26+
27+
def test_sort_by_date_passed(self, sample_conference):
28+
"""Test identifying passed deadlines."""
29+
# Future deadline
30+
sample_conference["cfp"] = "2030-12-31 23:59:00"
31+
conf = Conference(**sample_conference)
32+
assert not sort_yaml.sort_by_date_passed(conf)
33+
34+
# Past deadline
35+
sample_conference["cfp"] = "2020-01-01 00:00:00"
36+
conf = Conference(**sample_conference)
37+
assert sort_yaml.sort_by_date_passed(conf)
38+
39+
def test_sort_by_name(self, sample_conference):
40+
"""Test sorting by conference name."""
41+
conf = Conference(**sample_conference)
42+
sort_key = sort_yaml.sort_by_name(conf)
43+
44+
expected = f"{conf.conference} {conf.year}".lower()
45+
assert sort_key == expected
46+
47+
def test_order_keywords(self, sample_conference):
48+
"""Test keyword ordering according to schema."""
49+
conf = Conference(**sample_conference)
50+
ordered_conf = sort_yaml.order_keywords(conf)
51+
52+
# Should return a Conference object with ordered fields
53+
assert isinstance(ordered_conf, Conference)
54+
assert ordered_conf.conference == sample_conference["conference"]
55+
56+
def test_merge_duplicates(self):
57+
"""Test duplicate conference merging."""
58+
conferences = [
59+
{
60+
"conference": "PyCon Test",
61+
"year": 2025,
62+
"place": "Test City",
63+
"link": "https://short.link",
64+
"cfp": "2025-02-15 23:59:00",
65+
"start": "2025-06-01",
66+
"end": "2025-06-03",
67+
"sub": "PY",
68+
},
69+
{
70+
"conference": "PyCon Test",
71+
"year": 2025,
72+
"place": "Test City",
73+
"link": "https://much-longer-and-more-detailed.link", # Longer link
74+
"cfp": "2025-02-15 23:59:00",
75+
"start": "2025-06-01",
76+
"end": "2025-06-03",
77+
"sub": "PY",
78+
"sponsor": "https://sponsor.com", # Additional field
79+
},
80+
]
81+
82+
merged = sort_yaml.merge_duplicates(conferences)
83+
84+
# Should have only one conference after merging
85+
assert len(merged) == 1
86+
87+
# Should keep the longer link and additional fields
88+
merged_conf = merged[0]
89+
assert merged_conf["link"] == "https://much-longer-and-more-detailed.link"
90+
assert "sponsor" in merged_conf
91+
92+
def test_yaml_file_processing(self, temp_yaml_file, sample_conference):
93+
"""Test processing YAML conference files."""
94+
# Create test data
95+
test_data = [sample_conference]
96+
yaml_file = temp_yaml_file(test_data)
97+
98+
# Test loading
99+
with yaml_file.open(encoding="utf-8") as f:
100+
loaded_data = yaml.safe_load(f)
101+
102+
assert len(loaded_data) == 1
103+
assert loaded_data[0]["conference"] == "PyCon Test"
104+
105+
106+
class TestDateHandling:
107+
"""Test date parsing and timezone handling."""
108+
109+
def test_date_formats(self):
110+
"""Test various date format parsing."""
111+
valid_date_formats = ["2025-02-15 23:59:00", "2025-12-31 00:00:00", "2025-01-01 12:30:45"]
112+
113+
for date_str in valid_date_formats:
114+
# Should not raise an exception
115+
parsed_date = datetime.strptime(date_str, sort_yaml.dateformat).replace(tzinfo=timezone.utc)
116+
assert isinstance(parsed_date, datetime)
117+
118+
def test_tba_words_handling(self):
119+
"""Test handling of TBA/TBD words."""
120+
tba_variations = ["tba", "tbd", "cancelled", "none", "na", "n/a", "nan", "n.a."]
121+
122+
for tba_word in tba_variations:
123+
assert tba_word in sort_yaml.tba_words
124+
125+
def test_timezone_handling(self, sample_conference):
126+
"""Test timezone field handling."""
127+
# Test with timezone
128+
sample_conference["timezone"] = "America/New_York"
129+
conf = Conference(**sample_conference)
130+
assert conf.timezone == "America/New_York"
131+
132+
# Test without timezone (should be None/default to AoE)
133+
sample_conference["timezone"] = None
134+
conf = Conference(**sample_conference)
135+
assert conf.timezone is None
136+
137+
138+
class TestDataIntegrity:
139+
"""Test data integrity and consistency."""
140+
141+
def test_conference_data_consistency(self, sample_conference):
142+
"""Test that conference data maintains consistency."""
143+
conf = Conference(**sample_conference)
144+
145+
# Basic consistency checks
146+
assert conf.start <= conf.end
147+
assert conf.year >= 1989
148+
assert conf.conference.strip() != ""
149+
assert conf.place.strip() != ""
150+
151+
def test_required_fields_completeness(self, sample_conference):
152+
"""Test that all required fields are present and valid."""
153+
conf = Conference(**sample_conference)
154+
155+
required_fields = ["conference", "year", "link", "cfp", "place", "start", "end", "sub"]
156+
157+
for field in required_fields:
158+
assert hasattr(conf, field)
159+
value = getattr(conf, field)
160+
assert value is not None
161+
if isinstance(value, str):
162+
assert value.strip() != ""
163+
164+
def test_url_accessibility_format(self, sample_conference):
165+
"""Test URL format for accessibility."""
166+
# Test various valid URL formats
167+
valid_urls = [
168+
"https://example.com",
169+
"https://sub.example.com/path",
170+
"https://example.com/path?param=value",
171+
"https://example.org/path#section",
172+
]
173+
174+
for url in valid_urls:
175+
sample_conference["link"] = url
176+
conf = Conference(**sample_conference)
177+
assert str(conf.link).startswith(("http://", "https://"))
178+
179+
def test_date_logic_validation(self, sample_conference):
180+
"""Test logical date relationships."""
181+
# CFP should typically be before conference start
182+
from datetime import datetime
183+
184+
# In most cases, CFP should be before the conference
185+
# (Though not enforced as a hard rule since there might be exceptions)
186+
cfp_date = datetime.strptime(sample_conference["cfp"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
187+
start_date = sample_conference["start"]
188+
# Ensure both dates exist for logical validation
189+
assert cfp_date is not None
190+
assert start_date is not None

0 commit comments

Comments
 (0)