Nobody has started this yet — be first.
Business impact
The members list UI needs a "click a member, see their detail" view -- role, join date, email -- without re-fetching and re-scanning the entire member list client-side every time. The route exists (GET /api/tenant/members/<id>), but it is a placeholder that ignores the id in the URL and always returns whichever member happens to be first by join date. On any tenant with more than one member, clicking the second member (say, an admin someone wants to double-check the role of) silently shows the first member's (the owner's) data instead -- not an error a user would notice and report, but a confidently wrong answer that looks correct, which is worse: someone could make a role decision based on the wrong person's current role.
Problem
get_member(membership_id) ignores membership_id entirely and always returns the earliest-joined membership for the tenant.
Current behavior
get_member's body queries Membership.query.filter_by(tenant_id=g.tenant_uuid).order_by(Membership.joined_at.asc()).all() and returns members[0].to_dict() -- the membership_id path parameter is never referenced anywhere in the function.
Expected behavior
GET /api/tenant/members/<id> returns the membership matching that exact id, scoped to the current tenant, using the same tenant-scoping pattern as change_member_role/remove_member in the same file. An <id> that does not exist, or belongs to a different tenant, returns 404 {"error": "not_found", ...} -- not another tenant's member's data, and not a 500.
Steps to reproduce
Why this matters
This is a straightforward "the handler never uses one of its own parameters" bug, but it is worth noticing why it is dangerous rather than just obviously broken: it does not error. It returns valid-looking JSON, 200 OK, with real data -- just the wrong record. That is the general shape of a class of bugs that automated smoke tests ("does the endpoint return 200?") completely miss, and only a test that checks the content against a specific, known id catches.
Suggested approach
Look at change_member_role and remove_member immediately below this function in the same file -- both already do exactly the filter_by(id=..., tenant_id=g.tenant_uuid), 404-if-None pattern this route needs.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_06_member_detail_endpoint.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b feat/member-detail-endpointFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/member-detail-endpointSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inacmeglobexmake seed
*.localhost subdomains resolve to 127.0.0.1 out of the box in curl and every modern browser -- no /etc/hosts editing needed:
curl -s -X POST http://acme.localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
--cookie-jar /tmp/acme.cookies --cookie /tmp/acme.cookies \
-d '{"email": "owner@acme.test", "password": "owner-acme"}'
curl -s http://acme.localhost:5000/api/tenant/settings --cookie /tmp/acme.cookies
For tooling that cannot fake subdomains (some HTTP clients, CI), send X-Tenant-Slug instead -- honored only when TRUST_TENANT_HEADER=true (the Docker dev setup enables it; production does not):
curl -s http://localhost:5000/api/tenant/settings -H "X-Tenant-Slug: acme"
You do not strictly need Docker to work the tickets, but -- unlike a project that can fake its backing services -- this one genuinely needs a real Postgres and Redis, because the whole point is proving isolation against the real database, not a mock. Without Docker: cp .env.example .env, point DATABASE_URL/ADMIN_DATABASE_URL/TEST_DATABASE_URL/TEST_ADMIN_DATABASE_URL/REDIS_URL at a local Postgres/Redis (docker/init-db.sql documents the exact roles/databases the app expects -- notably the saas_admin/BYPASSRLS role migrations need and the plain saas role every request uses), then:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/flask db upgrade
.venv/bin/pytest practice_tickets/tests/test_ticket_07_webhook_signature_raw_body.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
./practice_tickets/run_tickets.sh 07 12 13 # just a few, by number
All 15 tests fail at the start; as you fix each ticket in app/ (or, for TICKET-12, in migrations/versions/0002_row_level_security.py, plus re-applying it), its test -- and only its test -- should flip to passing. make test runs the project's own main suite; several of its tests (RBAC, tenant-spoofing, and billing-webhook tests among them) fail at the start for the same underlying reasons as specific tickets -- fixing those tickets fixes those failures too, not a separate task.
Level 1
Fix a bug
Read existing behaviour, correct it.