fix: auto-resolve tenant_id on all team endpoints #378
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!378
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "372-auto-resolve-tenant-id"
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
Frontend never sends
tenant_id, causing 422 onGET /teams/{id}and all other team endpoints. This makestenant_idoptional on all 9 endpoints and auto-resolves the default tenant viaDEFAULT_TENANT_SLUGwhen omitted -- the same pattern already used inschedule.py.Changes
src/basketball_api/routes/teams.py-- importedDEFAULT_TENANT_SLUG, added_get_default_tenantand_resolve_tenanthelpers, madetenant_idoptional (defaultNone) on all 8 query-param endpoints, madeTeamCreate.tenant_idoptional for the body-param endpointtests/test_teams.py-- addedTestAutoResolveTenantclass with 11 test cases covering every endpoint withouttenant_id, plus backwards-compatibility testTest Plan
pytest tests/test_teams.py -v): 40 existing + 11 newtenant_idexplicitly still work (existing tests unchanged and passing)Review Checklist
_get_tenanthelper fromschedule.py)tenant_idtenant_idstill worksRelated Notes
Related
Frontend never sends tenant_id, causing 422 on GET /teams/{id} and all other team endpoints. Make tenant_id optional (default None) on all 9 endpoints and fall back to DEFAULT_TENANT_SLUG when omitted, matching the pattern already used in schedule.py. Closes forgejo_admin/basketball-api#372 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>PR #378 Review — QA Agent
Domain Review
Tech stack: Python 3.12 / FastAPI / SQLAlchemy 2.0 / Pydantic v2
Pattern match with schedule.py: Confirmed.
schedule.pyuses_get_tenant(db)which does the sameDEFAULT_TENANT_SLUGlookup with a 500 on missing tenant. Theteams.pyimplementation goes further by wrapping this in_resolve_tenant()which supports both auto-resolve fallback AND explicit id/slug — strictly better thanschedule.py's approach.Correctness: The
_resolve_tenanthelper correctly short-circuits to_get_default_tenantwhentenant_id is None, and delegates to_get_tenant_or_404for explicit values. All 9 endpoints updated consistently.TeamCreate.tenant_idchange fromint | strtoint | str | None = Noneis backwards-compatible.Multi-tenancy security: Tenant isolation preserved. Every endpoint resolves to a specific
Tenantobject and filters bytenant.id. Fallback only activates whentenant_idis omitted, not when invalid — invalid values still 404 via_get_tenant_or_404.Blockers
None.
Nits (non-blocking)
_get_default_tenantinteams.pyduplicates_get_tenantinschedule.pynearly verbatim. Consider extracting a sharedresolve_default_tenant(db)helper in a future cleanup.admin.pychanges (is_publicinAdminPlayerItem) from prior commit3c167ea. Minor scope creep — not a code quality issue.DEFAULT_TENANT_SLUGisn't seeded. Low priority (server misconfiguration scenario).SOP Compliance
372-auto-resolve-tenant-idCloses forgejo_admin/basketball-api#372Process
schedule.pyalready runs this approachVERDICT: APPROVE
PR #378 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Pydantic
Pattern consistency with schedule.py:
The
_get_default_tenanthelper in teams.py correctly mirrors the_get_tenanthelper inschedule.py(line 128). Both queryTenantbyDEFAULT_TENANT_SLUGand raise HTTP 500 if missing. The error messages differ slightly ("Default tenant not configured" vs "Tenant not configured") -- trivial, but noted.The added
_resolve_tenantwrapper is a clean addition that schedule.py does not need (schedule.py never accepts explicit tenant_id). This two-layer approach (_resolve_tenantdelegates to either_get_default_tenantor_get_tenant_or_404) is well-structured and correctly preserves backwards compatibility.Backwards compatibility: Confirmed. All 9 endpoints change
tenant_idfrom required toOptional[...] = None. When callers passtenant_idexplicitly,_resolve_tenantdelegates to_get_tenant_or_404which handles both int and slug -- no behavior change.Type correctness:
_resolve_tenantsignature is(db: Session, tenant_id: str | int | None) -> Tenant. TheTeamCreate.tenant_idfield isint | str | None = None. Query param endpoints declaretenant_id: str | None = None. All consistent with how FastAPI coerces query strings.DRY observation (not a blocker): The default-tenant-by-slug pattern is now duplicated in 5 files:
schedule.py,teams.py,admin.py(4 inline occurrences),register.py(2 inline), andpassword_reset.py(1 inline with its own_DEFAULT_TENANT_SLUGredeclaration). The teams.py PR correctly extracts it into a helper. The codebase would benefit from a shared utility, but that is out of scope for this bug fix.BLOCKERS
None.
Test coverage: 10 new tests (not 11 as the PR body states) in
TestAutoResolveTenantcover all 9 endpoints withouttenant_idplus 1 backwards-compatibility test. Each endpoint has at least one test exercising the auto-resolve path. This is adequate coverage.No unvalidated user input:
tenant_idflows through_get_tenant_or_404(which does a DB lookup by id or slug) or_get_default_tenant(hardcoded slug). No injection risk.No secrets in code. No credentials committed.
NITS
Scope creep --
is_publicfield in admin.py and test_admin_spa.py. Two of the four changed files (admin.py,test_admin_spa.py) addis_publictoAdminPlayerItemand its test assertion. This is unrelated to issue #372 (tenant auto-resolve). These changes should be in a separate PR tied to their own ticket. Not a blocker because the change is correct (the model field exists onPlayer), but it violates single-purpose PR discipline.PR body claims 11 tests but there are 10. The
TestAutoResolveTenantclass contains exactly 10 test methods. Minor documentation inaccuracy.Error message inconsistency.
_get_default_tenantsays "Default tenant not configured" whileschedule.py's_get_tenantsays "Tenant not configured". Consider matching the wording for grep-ability, or better yet, extracting a shared helper (future ticket)._resolve_tenant_idis now partially redundant. The old_resolve_tenant_idhelper (which parses strings to int) still exists alongside the new_resolve_tenant. They serve different purposes but the naming is confusing:_resolve_tenant_idvs_resolve_tenant. Consider renaming_resolve_tenant_idto_parse_tenant_idfor clarity.SOP COMPLIANCE
372-auto-resolve-tenant-idadmin.pyandtest_admin_spa.pycontainis_publicchanges unrelated to #372PROCESS OBSERVATIONS
is_publicadditions in admin.py should have their own issue and PR. Mixing concerns in a single PR makes rollback harder if either change causes problems.get_default_tenant()into a shared module would reduce divergence risk.VERDICT: APPROVED
The core fix is correct, well-tested, and follows the established pattern from schedule.py. The
is_publicscope creep is a process nit, not a correctness issue. The 10 tests adequately cover the auto-resolve behavior on all 9 endpoints plus backwards compatibility.36e5a16f7cd2107e240c