Extract shared bounty search helpers#357
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR centralizes bounty search logic into a reusable ChangesBounty Search Refactor
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR centralizes bounty search/filter logic into a reusable helper and adds targeted tests, while also tightening redirect “next” path handling.
Changes:
- Added
app/bounties.pywith shared normalization andsearch_bounties()query builder. - Refactored
app/main.pyendpoints/handlers to callsearch_bounties()instead of duplicating query logic. - Added pytest coverage for status normalization, LIKE wildcard escaping, and issue-number parsing limits.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/test_bounties.py | Adds tests validating search semantics (status, text, LIKE escaping, oversized issue numbers). |
| app/main.py | Reuses shared bounty search helper; hardens _safe_next_path by validating decoded paths too. |
| app/ledger/service.py | Simplifies session.execute(update(...)) result handling by removing casts/imports. |
| app/bounties.py | Introduces shared bounty search/normalization utilities used by API and MCP paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| claimed = session.execute( | ||
| update(Bounty) | ||
| .where(Bounty.id == bounty.id, Bounty.awards_paid < Bounty.max_awards) | ||
| .values( | ||
| awards_paid=Bounty.awards_paid + 1, | ||
| status=case( | ||
| (Bounty.awards_paid + 1 >= Bounty.max_awards, "paid"), | ||
| else_="open", | ||
| ), | ||
| ) | ||
| ) | ||
| if claimed.rowcount != 1: | ||
| raise LedgerError("bounty already paid") |
| query = query.order_by(Bounty.id.desc()) | ||
| if limit is not None: | ||
| query = query.limit(limit) | ||
| return list(session.scalars(query).all()) |
e82495d to
730623b
Compare
|
Updated to keep this PR focused on the bounty search extraction only:
Revalidated locally:
|
tolga-tom-nook
left a comment
There was a problem hiding this comment.
No blockers found on PR #357 at head 730623b.
Evidence checked:
- Inspected the extraction from
app/main.pyintoapp/bounties.py; the public/api/v1/bountiesand summary routes still wrap invalid status values as HTTP 400 while delegating status/text/issue-number search tosearch_bounties(). - Verified the LIKE escaping keeps
%,_, and\as literals, and that oversized numeric search terms are ignored instead of risking SQLite integer overflow. - Checked the new direct helper tests in
tests/test_bounties.pycover status normalization, text search, issue-number search, wildcard escaping, invalid status rejection, and oversized issue numbers.
Commands run locally:
./.venv/bin/python -m pytest tests/test_bounties.py tests/test_api_mcp.py -q→ 79 passed./.venv/bin/python -m ruff check app/bounties.py app/main.py tests/test_bounties.py→ passed./.venv/bin/python -m ruff format --check app/bounties.py app/main.py tests/test_bounties.py→ passed./.venv/bin/python -m mypy app→ passed./.venv/bin/python scripts/docs_smoke.py→ passedgit diff --check origin/main...HEAD→ passed
Karry2019web
left a comment
There was a problem hiding this comment.
Review ✅ APPROVED
FILES INSPECTED:
app/bounties.py— new module (62 lines) withnormalize_bounty_status,issue_number_search_value,search_bountiesapp/main.py— removed_issue_number_search_value,mcp_issue_number_search_value, inlined query-building inlist_bounties_by_status()and MCPlist_bountieshandler; replaced both withsearch_bounties()+ValueError→HTTPExceptionwrappertests/test_bounties.py— new test file (85 lines) with three focused tests
BEHAVIOR CHECKED:
normalize_bounty_status(" OPEN ")→"open"— preserves the same strip+lower normalization that was inlined in both REST and MCP handlers.frozenset({"open", "paid", "closed"})for constant-time membership check.issue_number_search_value: deduplicates the logic that was split across_issue_number_search_value(REST) andmcp_issue_number_search_value(MCP). Both were identical — this is a clean consolidation.search_bounities: Combines optional status filter + safe text search (with LIKE-escaped wildcards) + optional issue-number filter + ordering + limit. Uses keyword-only params (*) which prevents positional-mistake bugs.- REST
list_bounties_by_statushandler: Replaces 14 lines of inlined query logic withsearch_bounties(session, status=status, query_text=query_text)wrapped in try/except ValueError → 400. TheHTTPExceptionhandling for invalid status is preserved. - MCP
list_bountieshandler: Replaces 16 lines of inlined query logic withsearch_bounties(session, status=status, query_text=query_text, limit=list_limit_arg()). The existing guardstatus = optional_clean_str_arg("status") or "open"above keeps the default‑open behavior unchanged. - Variable rename
bounties→matching_bountiesinsubmit_work_proof(issue-number lookup branch) avoids shadowing the module-levelsearch_bountiesimport. Good practice — no behavior change.
TESTS VERIFIED:
test_search_bounties_filters_status_text_and_issue_number: Creates two bounties (one open, one paid). Asserts: status filter with whitespace (" OPEN ") finds only open;status="paid"finds only paid; text search"review-ready"matches title; digit text search"73"matches issue number. All pass with the samesqlite_urlfixture pattern.test_search_bounties_escapes_like_wildcards: Creates a bounty with%and_in title/acceptance fields. Assertsquery_text="%"matches only the literal‑percent bounty (not matching all),_works same,\works same. Critical edge-case test for LIKE-escaping correctness.test_search_bounties_rejects_invalid_status_and_ignores_oversized_issue: Verifies thatissue_number_search_valuereturnsNonefor values exceedingSQLITE_INTEGER_MAX, andsearch_bounties(status="bogus")raisesValueErrorwith the correct message. Uses in‑memory SQLite for the invalid‑status test so no fixture dependency.
CODE QUALITY:
- Clean deduplication: The exact same query logic was duplicated across REST and MCP handlers. Now unified in
app/bounties.py. - No new external dependencies — only
sqlalchemy+app.models.Bounty. - Error path:
ValueErrorfromnormalize_bounty_statusis caught and converted toHTTPException(400)in the REST handler. The MCP handler letsValueErrorpropagate naturally (MCP protocol handles 400‑equivalent). - The
from __future__ import annotationsconvention is consistent. - No whitespace churn in main.py — only the targeted removal of 2 functions + inlined query blocks.
- Good defensive patterns: SQL
LIKEescaping for%,_, and\;issue_number_search_valueguardsSQLITE_INTEGER_MAX(important since SQLite integers are 8‑byte signed).
SUMMARY: Textbook code‑health extraction. Eliminates duplicate query building, consolidates validation into one module, adds comprehensive edge‑case tests, and keeps behavior identical. APPROVED — no blockers.
|
Maintainer note after #320 filled: this PR still references closed bounty #320. If you want it considered for the current code-health round, rebase against main, update the bounty reference to #377, and keep the scope distinct from already accepted #328, #337, #364, #375, and #376. This is not accepted or paid as-is. |
Refs #320
Summary
app/bounties.pyand reuse it from both REST/public bounty listing and MCPlist_bounties.Complexity reduced
app/main.pyno longer owns two near-duplicate bounty search implementations for REST/page routes and MCP dispatch. Search escaping, numeric issue matching, ordering, status validation, and optional limits now live in one small module with direct tests.Tests
uv run --extra dev python -m pytest -q-> 338 passeduv run --extra dev python -m pytest tests/test_bounties.py tests/test_bounty_pages.py tests/test_api_mcp.py -q-> 85 passeduv run --extra dev ruff check .-> passeduv run --extra dev ruff format --check .-> 50 files already formatteduv run --extra dev python -m mypy app-> successuv run --extra dev python scripts/docs_smoke.py-> docs smoke okgit diff --check origin/main...HEAD-> cleanNo private keys, seed material, secrets, deployment credentials, private vulnerability details, payout credentials, or MRWK price claims are included.
Summary by CodeRabbit
Release Notes
New Features
Tests