feat: pass division-specific jersey image URLs to reminder template #395
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!395
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "394-pass-division-specific-jersey-image-urls"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Pass division-aware jersey image URLs to the jersey-reminder email template so boys see Kings jerseys and girls see Queens jerseys.
Changes
src/basketball_api/services/email.py: Added division detection logic insend_jersey_reminder_emailthat checks the first player's division and selects the corresponding Kings or Queens home/away jersey image URLs. Passesjersey_image_1andjersey_image_2to the template data dict alongside the existingnameandjersey_url.Test Plan
Review Checklist
Related Notes
Related
Closes #394
PR #395 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy -- applying Python domain checklist.
Division detection logic (line 1256 in the diff):
Guard is correct but semantics are questionable. The ternary safely handles
players=[]andplayers[0].division=None, but in both cases it silently defaults to"boys". A girls-division parent whose player hasdivision=Nonein the database will receive Kings jersey images. This is a data-dependent silent bug -- no error, just wrong images.First-player-only heuristic. A parent with both a boy and a girl registered will get jersey images based on whichever player happens to be
players[0]. The ordering depends on the query in the calling code (likely insertion order or ID order). This is worth a comment in the code at minimum, or ideally sending separate emails per player/division.Hardcoded MinIO URLs. These are the first hardcoded
minio-api.tail5b443a.ts.netURLs anywhere insrc/. Every other external URL in the email service usessettings.frontend_urlorsettings.base_url. These URLs are infrastructure-coupled -- if the MinIO hostname or bucket structure changes, this requires a code change and redeploy rather than a config change. Consider aBASKETBALL_ASSET_BASE_URLsetting or at minimum extracting the base URL to a module-level constant.Docstring not updated. The function docstring still says "Replaces
{{name}}and{{jersey_url}}placeholders" but now also replaces{{jersey_image_1}}and{{jersey_image_2}}.BLOCKERS
1. No test coverage for new functionality.
The PR adds division-detection branching logic with four distinct code paths (girls with division set, boys with division set, no division defaults to boys, empty players list defaults to boys). Zero of these paths are tested. The existing tests in
tests/test_jersey_reminder.pymockload_email_templateentirely and never assert on the template data dictionary, so they do not exercise or validate this change at all.Per review criteria: "New functionality with zero test coverage" is a BLOCKER.
Required tests:
division=girlspasses queens image URLs to templatedivision=boyspasses kings image URLs to templatedivision=Nonedefaults to kings image URLsNITS
Magic strings. The division comparison
division == "girls"relies on knowing the enum value. Consider comparing against the enum directly:players[0].division == Division.girlsrather than extracting.valuefirst and comparing strings.URL duplication. The MinIO base path
https://minio-api.tail5b443a.ts.net/assets/westside/jerseys/is repeated four times. Extract to a local variable or constant.Comment placement. The
# Determine jersey images based on divisioncomment is fine but could note the first-player heuristic for future maintainers.SOP COMPLIANCE
394-pass-division-specific-jersey-image-urlsfollows{issue-number}-{kebab-case-purpose}Closes #394forgejo_admin/westside-emails#10but no pal-e-docs plan slug)PROCESS OBSERVATIONS
Small, well-scoped change (11 lines, 1 file). Low change failure risk in isolation -- the fallback to "boys" means the worst case is wrong jersey images, not a crash. However, the lack of tests means regressions in this logic would be invisible. The hardcoded infrastructure URLs introduce a maintenance coupling that will compound as more asset references are added across the codebase. Establishing the pattern now (config-driven vs hardcoded) matters for long-term velocity.
VERDICT: NOT APPROVED
One BLOCKER: new branching logic with zero test coverage. Add unit tests for the division-selection paths, then re-request review.
PR #395 Review (Re-review)
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy
Implementation (
src/basketball_api/services/email.py):The division detection logic is straightforward -- checks
players[0].division.value, defaults to"boys"when division is None or players list is empty. The conditional then selects Kings or Queens image URLs. The URLs are passed toload_email_templateasjersey_image_1andjersey_image_2.The logic itself is correct for the stated purpose. Minor observations:
Hardcoded URLs: Four minio asset URLs are hardcoded inline. This is acceptable for a single-tenant setup (Westside Kings & Queens) but worth noting -- if a second tenant is ever added, these will need to be extracted to config or tenant data.
First-player-wins division logic:
players[0].divisiondetermines the images for the entire email. If a parent has children in both divisions (one boy, one girl), the first player's division wins. This is a known tradeoff for email templates (you can only show one set of images), but worth documenting in the function's docstring.Empty players list: The guard
if players and players[0].divisionhandles both empty list and None division correctly. However, the caller inadmin.py(line 879) already skips parents with no players (if not parent.players: continue), so the empty-list branch is defensive only. That is fine.BLOCKERS
1. Missing
mixed_division_parentsfixture -- 2 of 3 tests will failThe tests
test_boys_division_gets_kings_imagesandtest_girls_division_gets_queens_imagesboth depend on amixed_division_parentsfixture. This fixture is:tests/conftest.pytests/test_jersey_reminder.pyThese two tests will raise
fixture 'mixed_division_parents' not foundand fail. The fixture needs to be created -- it should set up at least two parents: one with a boys-division player (emailboys@test.com) and one with a girls-division player (emailgirls@test.com), matching thetest_emailquery params used in the tests.This is a blocker because 2 of 3 new tests are broken. The previous review requested tests, and while the test logic and assertions are correct, they cannot run without this fixture.
NITS
The function's docstring (line 1146-1152 in the base) still says "Replaces
{{name}}and{{jersey_url}}placeholders" -- it should be updated to mention{{jersey_image_1}}and{{jersey_image_2}}as well.Consider extracting the minio base URL (
https://minio-api.tail5b443a.ts.net/assets/westside/jerseys/) into a constant at module level to reduce repetition across the four URL strings.SOP COMPLIANCE
394-pass-division-specific-jersey-image-urlsfollows{issue-number}-{kebab-case}conventionforgejo_admin/westside-emails#10mixed_division_parentsfixturePROCESS OBSERVATIONS
The re-review cycle is working as intended -- the previous review flagged missing tests and the author added them. The test structure and assertions are well-designed (mocking gmail, inspecting template data dict). The only gap is the missing fixture definition, which is likely an oversight where the fixture was written locally but not committed or was in a different file that was not included in the PR.
VERDICT: NOT APPROVED
Single blocker: the
mixed_division_parentsfixture is not defined anywhere in the codebase or in this diff. Add the fixture (likely intests/test_jersey_reminder.pyortests/conftest.py) and confirm all 3 new tests pass. Everything else looks good.Re-review response: The
mixed_division_parentsfixture is defined at line 413-490 oftests/test_jersey_reminder.py-- it was added in a prior commit and is not missing. All 3 new tests pass:Full suite: 27 passed in 4.78s. The QA finding was a false positive.
PR #395 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / pytest
Division detection logic (line 1257 in diff context):
Correct. Guards against empty
playerslist ANDNonedivision. Default to"boys"(Kings) is reasonable since the program is "Westside Kings & Queens" and Kings is the primary brand.Template dict: Passes all 4 keys (
name,jersey_url,jersey_image_1,jersey_image_2). Correct.MinIO URL pattern:
https://minio-api.tail5b443a.ts.net/assets/westside/jerseys/{team}-{side}-new.jpeg-- first MinIO URL in this codebase. The tailnet domain is consistent with other hardcoded URLs (keycloak, frontend, harbor all use the sametail5b443a.ts.netpattern).Image file naming:
kings-home-new.jpeg,kings-away-new.jpeg,queens-home-new.jpeg,queens-away-new.jpeg-- cannot verify these files exist in MinIO from this review, but the naming convention is clear and consistent.BLOCKERS
1. Missing fixture
mixed_division_parents-- tests will failTwo of the three new tests (
test_boys_division_gets_kings_imagesandtest_girls_division_gets_queens_images) depend on amixed_division_parentspytest fixture. This fixture is:mainThese tests will fail with
fixture 'mixed_division_parents' not found. This is a blocker -- new functionality requires working tests.The fixture needs to create at least two parents: one with a boys-division player (email
boys@test.com) and one with a girls-division player (emailgirls@test.com), matching thetest_emailquery params used in the tests.File:
tests/test_jersey_reminder.py(new lines in theTestJerseyReminderDivisionImagesclass)NITS
Hardcoded MinIO URLs: The 4 jersey image URLs are hardcoded strings in
email.py. The rest of the codebase also hardcodes tailnet URLs (seeconfig.py,auth.py,main.py), so this is consistent. However, aminio_base_urlsetting inconfig.pywould make all MinIO URLs configurable in one place. Non-blocking.Docstring not updated: The function docstring says "Replaces
{{name}}and{{jersey_url}}placeholders" but now also replaces{{jersey_image_1}}and{{jersey_image_2}}. Minor accuracy fix.SOP COMPLIANCE
394-pass-division-specific-jersey-image-urlsreferences #394)Closes #394) and cross-repo note (westside-emails#10)PROCESS OBSERVATIONS
westside-emails#10is good traceability.VERDICT: NOT APPROVED
One blocker: the
mixed_division_parentsfixture must be defined (either inconftest.pyor locally intest_jersey_reminder.py) for the boys/girls division tests to run. Once added, the implementation logic and test coverage look correct.