Skip to content

Commit 5417e7f

Browse files
Grzegorz Kocjanclaude
andcommitted
test: naprawa 16 testów rozjechanych z kodem po refaktorach
Wszystkie wynikały z tego, że kod produkcyjny był refaktorowany bez aktualizacji testów — żadnej realnej usterki aplikacji. image_generator (10): prywatne helpery zmieniły nazwy przy przepisaniu generatora. Testy asertujące bezpośrednio na nich usunięte (decyzja GK: testujemy zachowanie, nie implementację) — _find_speaker_by_id, _get_speaker_avatar_download/_from_cache, _create_circular_mask; ten ostatni i tak duplikował test_apply_circular_mask. Testy publicznego API i face centeringu przepięte z _get_speaker_avatar na _avatar. Trzy testy językowe przekazywały pustą listę prelegentów do meetupu z prelekcją, przez co _find_speaker leciał StopIteration — dostają teraz prelegenta. models + integration (4): location_name i formatted_date przestały być property, są metodami z parametrem language. Dodatkowo lokalizacja daty (nazwa dnia, PL/EN) przeniosła się do image_generator._resolve_date_parts, więc test_models asertuje dziś faktyczny kontrakt modelu ("2024.06.27 18:00") zamiast nieistniejącego "CZWARTEK". Oczekiwanie w test_integration miało też zabłąkany cudzysłów i nieaktualne "budynek". cli (1): generate_all_meetups woła też get_speakers_for_meetup i _fetch_talks_data — fixture ich nie stubował, więc goły Mock trafiał do create_meetup_file i wywalał się na iteracji. pytest: 95 passed, 0 failed (było 83 passed / 16 failed). ruff czysty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 329a831 commit 5417e7f

6 files changed

Lines changed: 84 additions & 111 deletions

File tree

‎tests/test_cli.py‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
from datetime import date
2+
from io import BytesIO
23
from pathlib import Path
34
from unittest.mock import Mock, patch
45

56
import pytest
7+
from PIL import Image
68
from typer.testing import CliRunner
79

810
from pyldz.main import app
9-
from pyldz.models import Language, Meetup, MeetupStatus, MultiLanguage, Talk
11+
from pyldz.models import (
12+
File,
13+
Language,
14+
Meetup,
15+
MeetupStatus,
16+
MultiLanguage,
17+
Speaker,
18+
Talk,
19+
)
1020

1121

1222
@pytest.fixture
@@ -27,7 +37,20 @@ def mock_config(tmp_path):
2737

2838

2939
@pytest.fixture
30-
def mock_repository():
40+
def sample_speaker():
41+
buf = BytesIO()
42+
Image.new("RGBA", (300, 300), (255, 0, 0, 255)).save(buf, format="PNG")
43+
return Speaker(
44+
id="john-doe",
45+
name="John Doe",
46+
bio="A developer",
47+
avatar=File(name="avatar.png", content=buf.getvalue()),
48+
social_links=[],
49+
)
50+
51+
52+
@pytest.fixture
53+
def mock_repository(sample_speaker):
3154
with patch("pyldz.main.GoogleSheetsRepository") as mock_repo_class:
3255
repo_instance = Mock()
3356
mock_repo_class.return_value = repo_instance
@@ -60,6 +83,11 @@ def mock_repository():
6083
)
6184

6285
repo_instance.get_all_enabled_meetups.return_value = [sample_meetup]
86+
# HugoMeetupGenerator.generate_all_meetups sięga też po prelegentów
87+
# (get_speakers_for_meetup + _fetch_talks_data) — bez tych stubów
88+
# goły Mock trafia do create_meetup_file i wywala się na iteracji.
89+
repo_instance._fetch_talks_data.return_value = []
90+
repo_instance.get_speakers_for_meetup.return_value = [sample_speaker]
6391
yield repo_instance
6492

6593

‎tests/test_descriptions.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ def test_generate_meetup_com(sample_meetup_two_talks, sample_speaker, tmp_path):
182182
assert "Learn clean architecture." in description
183183
assert SocialMediaLinks.DISCORD in description
184184
assert "Cześć!" not in description
185-
assert "Podczas spotkania odbędą się niezwykle ciekawe prezentacje:" not in description
185+
assert (
186+
"Podczas spotkania odbędą się niezwykle ciekawe prezentacje:" not in description
187+
)
186188
assert "Zaproś innych!" not in description
187189
# Talks unified format: no numbering, em-dash separator
188190
assert "Clean Architecture — John Doe" in description
@@ -197,7 +199,9 @@ def test_generate_meetup_com_to_be_announced(
197199
sample_meetup_tba, sample_speaker, tmp_path
198200
):
199201
"""Test meetup.com description generation for a meetup without talks."""
200-
generator = MeetupDescriptionGenerator(sample_meetup_tba, [sample_speaker], tmp_path)
202+
generator = MeetupDescriptionGenerator(
203+
sample_meetup_tba, [sample_speaker], tmp_path
204+
)
201205

202206
description = generator.generate_meetup_com()
203207

@@ -231,7 +235,9 @@ def test_generate_youtube_live(sample_meetup_two_talks, sample_speaker, tmp_path
231235

232236
def test_youtube_live_agenda_tba_meetup(sample_meetup_tba, sample_speaker, tmp_path):
233237
"""TBA meetups keep generic agenda slots in YouTube live description."""
234-
generator = MeetupDescriptionGenerator(sample_meetup_tba, [sample_speaker], tmp_path)
238+
generator = MeetupDescriptionGenerator(
239+
sample_meetup_tba, [sample_speaker], tmp_path
240+
)
235241

236242
description = generator.generate_youtube_live()
237243

‎tests/test_face_centering.py‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ def test_detect_and_center_square_multiple_faces(monkeypatch, sample_rect_image)
6262

6363

6464
class TestImageGeneratorFaceCentering:
65-
def test_get_speaker_avatar_uses_centered_square_and_caching(
66-
self, tmp_path, monkeypatch
67-
):
65+
def test_avatar_uses_centered_square_and_caching(self, tmp_path, monkeypatch):
6866
# Prepare assets dir with minimal structure
6967
assets_dir = tmp_path / "assets"
7068
(assets_dir / "images" / "avatars").mkdir(parents=True)
@@ -105,7 +103,7 @@ def fake_center(img: Image.Image) -> Image.Image:
105103

106104
monkeypatch.setattr(face_centering, "detect_and_center_square", fake_center)
107105

108-
result = generator._get_speaker_avatar(speaker, (300, 300))
106+
result = generator._avatar(speaker, (300, 300))
109107

110108
assert result is not None
111109
assert result.size == (300, 300)
@@ -114,9 +112,7 @@ def fake_center(img: Image.Image) -> Image.Image:
114112
processed_cache = generator.cache_dir / f"{speaker.id}.png"
115113
assert processed_cache.exists()
116114

117-
def test_get_speaker_avatar_no_face_detected_uses_fallback(
118-
self, tmp_path, monkeypatch
119-
):
115+
def test_avatar_no_face_detected_uses_fallback(self, tmp_path, monkeypatch):
120116
"""Test that when face detection fails, avatar is used without face centering."""
121117
# Prepare assets dir with minimal structure
122118
assets_dir = tmp_path / "assets"
@@ -160,7 +156,7 @@ def fake_center_no_face(img: Image.Image) -> Image.Image:
160156
)
161157

162158
# Should not raise, should return the avatar without face centering
163-
result = generator._get_speaker_avatar(speaker, (300, 300))
159+
result = generator._avatar(speaker, (300, 300))
164160

165161
assert result is not None
166162
assert result.size == (300, 300)

‎tests/test_image_generator.py‎

Lines changed: 32 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from PIL import Image
66

7-
from pyldz.image_generator import ImageGenerationError, MeetupImageGenerator
7+
from pyldz.image_generator import MeetupImageGenerator
88
from pyldz.models import File, Language, Meetup, MultiLanguage, Speaker, Talk
99

1010

@@ -169,8 +169,8 @@ def test_generate_featured_image_solo(
169169
generator = MeetupImageGenerator(temp_assets_dir)
170170
output_path = tmp_path / "featured.png"
171171

172-
with patch.object(generator, "_get_speaker_avatar") as mock_get_avatar:
173-
mock_get_avatar.return_value = Image.new("RGBA", (300, 300), (255, 0, 0, 255))
172+
with patch.object(generator, "_avatar") as mock_avatar:
173+
mock_avatar.return_value = Image.new("RGBA", (300, 300), (255, 0, 0, 255))
174174

175175
result = generator.generate_featured_image(
176176
sample_meetup, [sample_speaker], output_path
@@ -186,8 +186,8 @@ def test_generate_featured_image_duo(
186186
generator = MeetupImageGenerator(temp_assets_dir)
187187
output_path = tmp_path / "featured.png"
188188

189-
with patch.object(generator, "_get_speaker_avatar") as mock_get_avatar:
190-
mock_get_avatar.return_value = Image.new("RGBA", (240, 240), (255, 0, 0, 255))
189+
with patch.object(generator, "_avatar") as mock_avatar:
190+
mock_avatar.return_value = Image.new("RGBA", (240, 240), (255, 0, 0, 255))
191191

192192
result = generator.generate_featured_image(
193193
sample_duo_meetup, sample_speakers, output_path
@@ -197,61 +197,6 @@ def test_generate_featured_image_duo(
197197
assert output_path.exists()
198198

199199

200-
def test_find_speaker_by_id(temp_assets_dir, sample_speakers):
201-
"""Test finding speaker by ID."""
202-
generator = MeetupImageGenerator(temp_assets_dir)
203-
204-
speaker = generator._find_speaker_by_id(sample_speakers, "john-doe")
205-
assert speaker is not None
206-
assert speaker.name == "John Doe"
207-
208-
speaker = generator._find_speaker_by_id(sample_speakers, "nonexistent")
209-
assert speaker is None
210-
211-
212-
def test_get_speaker_avatar_download(temp_assets_dir):
213-
generator = MeetupImageGenerator(temp_assets_dir)
214-
215-
# Create a test image in memory
216-
test_image = Image.new("RGB", (100, 100), (255, 0, 0))
217-
from io import BytesIO
218-
219-
img_bytes = BytesIO()
220-
test_image.save(img_bytes, format="PNG")
221-
img_bytes.seek(0)
222-
223-
speaker = Speaker(
224-
id="john-doe",
225-
name="John Doe",
226-
bio="A developer",
227-
avatar=File(name="avatar.png", content=img_bytes.getvalue()),
228-
social_links=[],
229-
)
230-
231-
avatar = generator._get_speaker_avatar(speaker, (50, 50))
232-
233-
assert avatar is not None
234-
assert avatar.size == (50, 50)
235-
236-
# Check that avatar was cached
237-
cache_file = generator.cache_dir / f"{speaker.id}.png"
238-
assert cache_file.exists()
239-
240-
241-
def test_get_speaker_avatar_from_cache(temp_assets_dir, sample_speaker):
242-
generator = MeetupImageGenerator(temp_assets_dir)
243-
244-
# Create cached avatar
245-
cache_file = generator.cache_dir / f"{sample_speaker.id}.png"
246-
test_image = Image.new("RGBA", (100, 100), (0, 255, 0, 255))
247-
test_image.save(cache_file)
248-
249-
avatar = generator._get_speaker_avatar(sample_speaker, (50, 50))
250-
251-
assert avatar is not None
252-
assert avatar.size == (50, 50)
253-
254-
255200
def test_apply_circular_mask(temp_assets_dir):
256201
generator = MeetupImageGenerator(temp_assets_dir)
257202

@@ -274,59 +219,61 @@ def test_apply_circular_mask_missing_mask_file(temp_assets_dir):
274219
assert masked.mode == "RGBA"
275220

276221

277-
def test_create_circular_mask(temp_assets_dir):
278-
generator = MeetupImageGenerator(temp_assets_dir)
279-
280-
test_image = Image.new("RGBA", (100, 100), (255, 0, 0, 255))
281-
masked = generator._create_circular_mask(test_image)
282-
283-
assert masked.size == test_image.size
284-
assert masked.mode == "RGBA"
285-
286-
287-
def test_generate_featured_image_polish_language(temp_assets_dir, sample_meetup):
222+
def test_generate_featured_image_polish_language(
223+
temp_assets_dir, sample_meetup, sample_speaker
224+
):
288225
"""Test generating featured image in Polish language."""
289226
generator = MeetupImageGenerator(temp_assets_dir)
290227
output_path = temp_assets_dir / "featured-pl.png"
291228

292229
# Generate image in Polish
293-
result = generator.generate_featured_image(
294-
sample_meetup, [], output_path, Language.PL
295-
)
230+
with patch.object(generator, "_avatar") as mock_avatar:
231+
mock_avatar.return_value = Image.new("RGBA", (300, 300), (255, 0, 0, 255))
232+
result = generator.generate_featured_image(
233+
sample_meetup, [sample_speaker], output_path, Language.PL
234+
)
296235

297236
assert result == output_path
298237
assert output_path.exists()
299238
assert output_path.suffix == ".png"
300239

301240

302-
def test_generate_featured_image_english_language(temp_assets_dir, sample_meetup):
241+
def test_generate_featured_image_english_language(
242+
temp_assets_dir, sample_meetup, sample_speaker
243+
):
303244
"""Test generating featured image in English language."""
304245
generator = MeetupImageGenerator(temp_assets_dir)
305246
output_path = temp_assets_dir / "featured-en.png"
306247

307248
# Generate image in English
308-
result = generator.generate_featured_image(
309-
sample_meetup, [], output_path, Language.EN
310-
)
249+
with patch.object(generator, "_avatar") as mock_avatar:
250+
mock_avatar.return_value = Image.new("RGBA", (300, 300), (255, 0, 0, 255))
251+
result = generator.generate_featured_image(
252+
sample_meetup, [sample_speaker], output_path, Language.EN
253+
)
311254

312255
assert result == output_path
313256
assert output_path.exists()
314257
assert output_path.suffix == ".png"
315258

316259

317-
def test_generate_featured_image_both_languages(temp_assets_dir, sample_meetup):
260+
def test_generate_featured_image_both_languages(
261+
temp_assets_dir, sample_meetup, sample_speaker
262+
):
318263
"""Test generating featured images in both languages."""
319264
generator = MeetupImageGenerator(temp_assets_dir)
320265
pl_path = temp_assets_dir / "featured-pl.png"
321266
en_path = temp_assets_dir / "featured-en.png"
322267

323268
# Generate both versions
324-
pl_result = generator.generate_featured_image(
325-
sample_meetup, [], pl_path, Language.PL
326-
)
327-
en_result = generator.generate_featured_image(
328-
sample_meetup, [], en_path, Language.EN
329-
)
269+
with patch.object(generator, "_avatar") as mock_avatar:
270+
mock_avatar.return_value = Image.new("RGBA", (300, 300), (255, 0, 0, 255))
271+
pl_result = generator.generate_featured_image(
272+
sample_meetup, [sample_speaker], pl_path, Language.PL
273+
)
274+
en_result = generator.generate_featured_image(
275+
sample_meetup, [sample_speaker], en_path, Language.EN
276+
)
330277

331278
assert pl_result.exists()
332279
assert en_result.exists()

‎tests/test_integration.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
LocationRepository,
1313
MeetupStatus,
1414
MultiLanguage,
15-
Speaker,
1615
_MeetupRow,
1716
_TalkRow,
1817
)
@@ -298,7 +297,9 @@ def test_complete_data_flow_single_meetup(
298297
assert meetup.title == "Meetup #58"
299298
assert meetup.date == date(2025, 5, 28)
300299
assert meetup.time == "18:00"
301-
assert meetup.location_name == 'IndieBI, Piotrkowska 157A, budynek Hi Piotrkowska"'
300+
assert (
301+
meetup.location_name(Language.PL) == "IndieBI, Piotrkowska 157A, Hi Piotrkowska"
302+
)
302303
# featured field removed in current model
303304
assert meetup.status == MeetupStatus.PUBLISHED
304305
assert (
@@ -689,7 +690,7 @@ def test_model_integration_and_validation():
689690
assert meetup.talks[0].speaker_id == speaker.id
690691
assert meetup.talks[0].language == Language.EN
691692
assert speaker.name == "John Doe"
692-
assert meetup.location_name == "Test Venue EN"
693+
assert meetup.location_name(Language.EN) == "Test Venue EN"
693694

694695

695696
def test_speaker_with_missing_photo_url_uses_fallback():

‎tests/test_models.py‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,9 @@ def test_meetup_formatted_date_polish():
200200
language=Language.PL,
201201
)
202202

203-
formatted = meetup.formatted_date
204-
assert "CZWARTEK" in formatted
205-
assert "27.06.2024" in formatted
206-
assert "18:00" in formatted
203+
# Nazwa dnia i lokalizacja PL/EN przeniosły się do
204+
# MeetupImageGenerator._resolve_date_parts; model zwraca surowy stempel.
205+
assert meetup.formatted_date(Language.PL) == "2024.06.27 18:00"
207206

208207

209208
def test_meetup_formatted_date_english():
@@ -218,9 +217,5 @@ def test_meetup_formatted_date_english():
218217
language=Language.EN,
219218
)
220219

221-
formatted = meetup.formatted_date
222-
assert "THURSDAY" in formatted
223-
assert "June" in formatted
224-
assert "27" in formatted
225-
assert "2024" in formatted
226-
assert "18:00" in formatted
220+
# Stempel jest niezależny od języka — patrz test powyżej.
221+
assert meetup.formatted_date(Language.EN) == "2024.06.27 18:00"

0 commit comments

Comments
 (0)