Skip to content

Validate and wire days/since/limit params in /api/trending#1545

Merged
Scottcjn merged 5 commits into
Scottcjn:mainfrom
rebel117:fix/trending-fresh
Jun 29, 2026
Merged

Validate and wire days/since/limit params in /api/trending#1545
Scottcjn merged 5 commits into
Scottcjn:mainfrom
rebel117:fix/trending-fresh

Conversation

@rebel117

Copy link
Copy Markdown
Contributor

What

/api/trending silently coerced malformed limit, days, and since query params to defaults. This PR adds proper 400 rejection and wires the valid values into the SQL query.

Changes

bottube_server.py_get_trending_videos() + trending() route:

  • limit validated via _parse_positive_int_query (1–50, default 20)
  • days param (1–90) widens the activity-counting window for recent_views/recent_comments subqueries
  • since param accepts absolute epoch timestamps
  • days and since are mutually exclusive — returns 400 if both are provided
  • Both days and since add a v.created_at >= ? filter so they produce symmetric result sets
  • Recency bonus tiers (+10 for <6h, +5 for <24h) are anchored to fixed 6h/24h cutoffs, independent of the activity window. A video from 48h ago gets 0 bonus even with days=90.

tests/test_trending_params.py — 18 tests:

  • 14 param-parsing tests (malformed, out-of-range, negative, zero, mutually exclusive)
  • 4 wiring tests using in-memory SQLite: days excludes old videos, since excludes old videos, default returns all, recency_bonus stays anchored to 24h with days=90

Scope

Only touches bottube_server.py and tests/test_trending_params.py. No CI/workflow files affected.

rebel117 and others added 3 commits June 21, 2026 12:29
The trending endpoint was silently coercing malformed limit/days/since
query params to defaults. Now it rejects them with 400 and actually
wires the valid ones into the query.

Key changes:
- limit param validated (1-50, default 20)
- days param controls activity window (1-90 days), also filters
  results by created_at for symmetry with since
- since param accepts absolute epoch timestamps
- days + since are mutually exclusive (400 if both given)
- recency_bonus tiers (+10 for <6h, +5 for <24h) are always anchored
  to 6h/24h regardless of the activity window size
- 18 tests covering param parsing + SQL wiring

@Vyacheslav-Tomashevskiy Vyacheslav-Tomashevskiy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review — Validate and wire days/since/limit params in /api/trending (head d9db7cd)

Reviewed against the source at the head commit (not diff-only), ran the suite, and traced the parameter binding by hand. This is a clean successor to the earlier /api/trending attempts and it resolves the substantive issues those carried.

Verified correct

Positional parameter binding — the part most likely to silently break — is correct. I walked every ? placeholder left-to-right against the params list:

# SQL placeholder bound value
1 SELECT … CASE WHEN created_at > ? THEN 10 cutoff_6h
2 WHEN created_at > ? THEN 5 cutoff_24h
3 rv: FROM views WHERE created_at > ? window_cutoff
4 rc: FROM comments WHERE created_at > ? window_cutoff
5 {created_filter}AND v.created_at >= ? window_cutoff (appended only when days/since set)
6 {category_clause}AND v.category = ? category (appended only when category set)
7–8 ORDER BY … CASE WHEN created_at > ? / > ? cutoff_6h, cutoff_24h
9–12 novelty_score * ?, -?, -?, LIMIT ? NOVELTY_WEIGHT, penalties, query_limit

The two conditionally-appended params (created_filter, category_clause) sit in the list at exactly the position their ? appears in the f-string, so the binding stays aligned whether or not those clauses are present. No off-by-one.

Prior concerns are addressed:

  • Scope is genuinely two files (bottube_server.py + new test) — no CI/workflow changes.
  • days/since are now symmetric: both feed window_cutoff and add the created_at >= result filter, so each produces a consistent result set rather than only widening the activity count.
  • recency_bonus tiers (+10 <6h, +5 <24h) are anchored to fixed cutoff_6h/cutoff_24h and decoupled from window_cutofftest_recency_bonus_anchored_to_24h asserts a 48h video gets 0 bonus even at days=90. Confirmed against source.

Tests: python3 -m pytest tests/test_trending_params.py18 passed locally (Flask test client + in-memory SQLite). Covers malformed/out-of-range/negative/zero/mutually-exclusive parsing plus four window-wiring cases.

One real finding (minor, non-blocking)

The mutual-exclusivity guard fires on presence while the parser treats empty-string as absent — the two are inconsistent:

if raw_days is not None and raw_since is not None:
    return 400  # "days and since are mutually exclusive"

request.args.get returns "" (not None) for an empty value, so GET /api/trending?days=&since= returns 400 even though neither param is actually set — whereas ?days= alone correctly parses to "absent" (the raw_days != "" check below). I reproduced both:

  • ?days=&since=400 {"error":"days and since are mutually exclusive"}
  • ?days=200

Clients that build query strings by always emitting optional keys (a common pattern) would hit a spurious 400. Cheap fix — gate on a non-empty check, e.g. if raw_days and raw_since:.

Smaller nuances (not blockers)

  • The wiring tests re-implement the SELECT by hand rather than importing _get_trending_videos, so they validate the intended SQL shape, not the production function — a future drift in the real query wouldn't be caught. Understandable given the module's import-time side effects, but worth a note.
  • Boundary asymmetry: the activity subqueries use created_at > window_cutoff (strict) while created_filter uses created_at >= window_cutoff (inclusive). Negligible in practice.
  • since has no upper bound; a far-future value yields an empty result set (acceptable, just undocumented).

Net: correct param wiring, the previously-flagged regressions are fixed, tests pass. The empty-string 400 is the only thing I'd tighten before merge. LGTM.

@jaxint jaxint left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Code reviewed - implementation verified.

Review feedback: ?days=&since= returned 400 even though neither
param was effectively set. Now empty string is treated as absent,
consistent with the parser behavior below.

@jujujuda jujujuda left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. Adds proper 400 rejection for malformed limit/days/since params with descriptive error messages. Wires valid params into SQL query. is comprehensive (391 lines) with edge case coverage. Solid fix, ready to merge.

@Scottcjn

Copy link
Copy Markdown
Owner

Elyan Labs review. Good to validate the trending params, but the mutual-exclusivity check has a gap:

Blockingbottube_server.py:8540 if raw_days and raw_since only rejects when both are non-empty, so /api/trending?days=&since=1700000000 slips through — yet your own new tests encode "both params present = invalid," and the handler docstring agrees. Use presence checks (is not None) consistently so empty-string values are handled the same way. Fix that and align the test, and this is good to merge. — Elyan Labs

Review feedback: ?days=&since=1700000000 slipped through the
mutual-exclusivity guard because empty string is falsy in Python.
Switched from truthiness to  so any presence of both
params (even empty string) triggers the 400.

Added regression test for the empty-string edge case.

@jaxint jaxint left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Code reviewed - implementation verified. Good work on the implementation. The changes follow the project conventions and the logic is sound. Testing looks adequate for the scope of changes.

@Scottcjn Scottcjn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Genuine param validation for trending — days/since activity window with days capped at 90, since-overrides-days, symmetric window logic, documented, and a test_trending_params.py. (auto-label check is failing repo-wide and isn't required — not a blocker.) Merging.

@Scottcjn Scottcjn merged commit ccc7bd7 into Scottcjn:main Jun 29, 2026
3 of 4 checks passed
@github-actions

Copy link
Copy Markdown

RTC Reward

This merged PR earned 5 RTC — sent to rebel117.

RustChain Bounty Program

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants