Nobody has started this yet — be first.
Business impact
SLUG_MIN_LEN = 3 and SLUG_MAX_LEN = 32 are the documented, advertised bounds for a custom slug. A user who picks a slug at exactly one of those boundaries -- a short, memorable 3-letter brand tag like "ibm", or a slug padded out to exactly the max length -- gets a 400 telling them their perfectly valid input is invalid. This is a classic "off-by-one that generates support tickets": nothing crashes, nothing corrupts data, but every user who happens to land exactly on a boundary the API itself claims to allow gets bounced, and will not understand why.
Problem
_validate_custom_slug checks if not (SLUG_MIN_LEN < len(slug) < SLUG_MAX_LEN). Both comparisons are strict (<), which excludes the boundary values themselves.
Current behavior
A slug of exactly 3 characters or exactly 32 characters fails this check and the API returns 400, even though the endpoint's own error message says "must be between 3 and 32 characters."
Expected behavior
A custom_slug of exactly SLUG_MIN_LEN (3) or exactly SLUG_MAX_LEN (32) characters is accepted (201), matching what SLUG_MIN_LEN/SLUG_MAX_LEN are documented to mean.
Steps to reproduce
Why this matters
SLUG_MIN_LEN/SLUG_MAX_LEN are named as inclusive bounds ("must be between 3 and 32 characters"), the plain-English reading of the error message the code itself returns on rejection. A range check whose comparison operators do not match the semantics implied by its own error message is a classic boundary bug -- invisible in almost every manual test (nobody tries exactly the edge value first) and only shows up once real user input starts hitting the edges.
Suggested approach
Compare what the error message says ("between 3 and 32 characters", inclusive by normal English usage) against what the comparison operators in the if actually check. Fix the operators to match the stated semantics, not the other way around.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_02_custom_slug_boundary.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 fix/custom-slug-boundaryFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/custom-slug-boundarySubmit 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 indocker-entrypoint.shcurl -X POST http://localhost:5000/api/urls \
-H "Content-Type: application/json" \
-d '{"target_url": "https://anthropic.com", "custom_slug": "claude"}'
# -> {"slug": "claude", "short_url": "http://localhost:5000/claude", "expires_at": null}
curl -i http://localhost:5000/claude # 302 redirect, records a click
make seed # or: docker compose exec web flask seed-db
If something is already listening on 5432/6379 locally, uncomment POSTGRES_HOST_PORT / REDIS_HOST_PORT in .env to remap the host side — the app always talks to db/redis over the compose network regardless. Run production-style (gunicorn, no dev server, no bind mount) with docker compose -f docker-compose.prod.yml up --build. GET /healthz checks both Postgres and Redis connectivity.
You do not need Docker running at all to work the tickets — the test suite uses TestingConfig (SQLite in-memory via StaticPool) and fakeredis instead of a real Postgres/Redis, so everything runs against a plain virtualenv:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/pytest practice_tickets/tests/test_ticket_01_base62_roundtrip.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 20, clean pass/fail summary
./practice_tickets/run_tickets.sh 05 13 16 # just a few, by number
All 20 tests fail at the start; as you fix each ticket in app/, its test (and only its test) should flip to passing. .venv/bin/pytest tests/ (via make test) runs the project's own main suite — some ticket bugs (TICKET-05, 07, 08, 09, 14 among others) also break assertions in that suite, since they are real bugs in shared code paths; fixing the ticket fixes those too, not a separate task.
Level 1
Fix a bug
Read existing behaviour, correct it.