Skip to content

Extract shared bounty search helpers#357

Open
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-public-page-helpers
Open

Extract shared bounty search helpers#357
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-public-page-helpers

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Refs #320

Summary

  • Extract shared bounty status/query search into app/bounties.py and reuse it from both REST/public bounty listing and MCP list_bounties.
  • Add focused helper tests for status normalization, issue-number search, LIKE wildcard escaping, invalid status handling, and limit behavior.
  • Keep the PR scoped to bounty search/query consolidation only.

Complexity reduced

app/main.py no 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 passed
  • uv run --extra dev python -m pytest tests/test_bounties.py tests/test_bounty_pages.py tests/test_api_mcp.py -q -> 85 passed
  • uv run --extra dev ruff check . -> passed
  • uv run --extra dev ruff format --check . -> 50 files already formatted
  • uv run --extra dev python -m mypy app -> success
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok
  • git diff --check origin/main...HEAD -> clean

No 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

    • Added bounty search with filtering by status and custom query text
    • Search queries properly escape and handle special characters
    • Issue numbers in search queries are automatically detected and matched
  • Tests

    • Added comprehensive test coverage for bounty search functionality and edge cases

Review Change Stack

Copilot AI review requested due to automatic review settings May 26, 2026 01:41
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5586dc8b-762f-4cc7-a922-243b2288d127

📥 Commits

Reviewing files that changed from the base of the PR and between ac99d79 and 730623b.

📒 Files selected for processing (3)
  • app/bounties.py
  • app/main.py
  • tests/test_bounties.py

📝 Walkthrough

Walkthrough

This PR centralizes bounty search logic into a reusable search_bounties utility function in a new app/bounties.py module, then refactors the web API and MCP server endpoints to use it instead of maintaining duplicate inline SQL filtering. The utility includes status validation, case-insensitive text search with explicit LIKE wildcard escaping, numeric issue-number lookup, and result ordering/limiting.

Changes

Bounty Search Refactor

Layer / File(s) Summary
Bounty Search Utility Implementation
app/bounties.py
Defines SQLITE_INTEGER_MAX and VALID_BOUNTY_STATUSES constants, normalize_bounty_status() to validate/normalize status input, issue_number_search_value() to parse numeric query strings with bounds checking, and search_bounties() to build a SQLAlchemy query with optional status/text/issue-number filtering, LIKE character escaping, descending ID ordering, and result limiting.
Search Utility Tests
tests/test_bounties.py
Three test cases covering status/text/issue-number filtering in open and paid bounty states, LIKE wildcard character escaping (%, _, \), and validation of error cases (invalid status, oversized issue numbers).
Web API Integration
app/main.py
Updates list_bounties_by_status REST endpoint to use search_bounties() with error-to-HTTP-400 conversion, removes the _issue_number_search_value helper, and adds the search_bounties import.
MCP Server Integration
app/main.py
Updates MCP list_bounties tool and submit_work_proof issue-number branch to use search_bounties() for consistent filtering and lookup, removes the mcp_issue_number_search_value helper, and preserves existing validation behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

mrwk:paid

Suggested reviewers

  • MolhamHamwi
  • weilixiong
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Extract shared bounty search helpers' directly and clearly describes the main change: extracting bounty search logic into a reusable module.
Description check ✅ Passed The description includes summary, evidence of the issue being addressed, comprehensive test results for all required checks, and related issue reference, meeting the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py with shared normalization and search_bounties() query builder.
  • Refactored app/main.py endpoints/handlers to call search_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.

Comment thread app/ledger/service.py Outdated
Comment on lines 659 to 671
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")
Comment thread app/bounties.py
query = query.order_by(Bounty.id.desc())
if limit is not None:
query = query.limit(limit)
return list(session.scalars(query).all())
@TUPM96 TUPM96 force-pushed the codex/bounty-320-public-page-helpers branch from e82495d to 730623b Compare May 26, 2026 01:48
@TUPM96
Copy link
Copy Markdown
Contributor Author

TUPM96 commented May 26, 2026

Updated to keep this PR focused on the bounty search extraction only:

  • Removed the unrelated ledger typing cleanup from the branch.
  • Removed the unrelated OAuth redirect hardening from the branch.
  • Kept the small matching_bounties local rename needed by mypy after search_bounties() gives the earlier bounties variable a concrete list[Bounty] type in the same dispatcher function.

Revalidated locally:

  • uv run --extra dev python -m pytest -q -> 338 passed.
  • uv run --extra dev python -m pytest tests/test_bounties.py tests/test_bounty_pages.py tests/test_api_mcp.py -q -> 85 passed.
  • uv run --extra dev ruff check . -> passed.
  • uv run --extra dev ruff format --check . -> 50 files already formatted.
  • uv run --extra dev python -m mypy app -> success.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check origin/main...HEAD -> clean.

Copy link
Copy Markdown
Contributor

@tolga-tom-nook tolga-tom-nook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers found on PR #357 at head 730623b.

Evidence checked:

  • Inspected the extraction from app/main.py into app/bounties.py; the public /api/v1/bounties and summary routes still wrap invalid status values as HTTP 400 while delegating status/text/issue-number search to search_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.py cover 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 → passed
  • git diff --check origin/main...HEAD → passed

Copy link
Copy Markdown
Contributor

@Karry2019web Karry2019web left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review ✅ APPROVED

FILES INSPECTED:

  • app/bounties.py — new module (62 lines) with normalize_bounty_status, issue_number_search_value, search_bounties
  • app/main.py — removed _issue_number_search_value, mcp_issue_number_search_value, inlined query-building in list_bounties_by_status() and MCP list_bounties handler; replaced both with search_bounties() + ValueErrorHTTPException wrapper
  • tests/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) and mcp_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_status handler: Replaces 14 lines of inlined query logic with search_bounties(session, status=status, query_text=query_text) wrapped in try/except ValueError → 400. The HTTPException handling for invalid status is preserved.
  • MCP list_bounties handler: Replaces 16 lines of inlined query logic with search_bounties(session, status=status, query_text=query_text, limit=list_limit_arg()). The existing guard status = optional_clean_str_arg("status") or "open" above keeps the default‑open behavior unchanged.
  • Variable rename bountiesmatching_bounties in submit_work_proof (issue-number lookup branch) avoids shadowing the module-level search_bounties import. 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 same sqlite_url fixture pattern.
  • test_search_bounties_escapes_like_wildcards: Creates a bounty with % and _ in title/acceptance fields. Asserts query_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 that issue_number_search_value returns None for values exceeding SQLITE_INTEGER_MAX, and search_bounties(status="bogus") raises ValueError with 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: ValueError from normalize_bounty_status is caught and converted to HTTPException(400) in the REST handler. The MCP handler lets ValueError propagate naturally (MCP protocol handles 400‑equivalent).
  • The from __future__ import annotations convention is consistent.
  • No whitespace churn in main.py — only the targeted removal of 2 functions + inlined query blocks.
  • Good defensive patterns: SQL LIKE escaping for %, _, and \; issue_number_search_value guards SQLITE_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.

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 26, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants