Skip to content

fix: implement ageInSeconds strict staleness filter#1108

Open
3rabiii wants to merge 3 commits into
OneBusAway:mainfrom
3rabiii:fix-vehicles-for-agency-gap-2
Open

fix: implement ageInSeconds strict staleness filter#1108
3rabiii wants to merge 3 commits into
OneBusAway:mainfrom
3rabiii:fix-vehicles-for-agency-gap-2

Conversation

@3rabiii

@3rabiii 3rabiii commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes a spec gap by implementing the ageInSeconds staleness filter for the vehicles-for-agency endpoint.

Problem

The handler previously ignored the ageInSeconds parameter, returning all tracked vehicles regardless of age. Additionally, the legacy Java implementation contained a documented defect where ageInSeconds=0 silently disabled the filter instead of applying a strict 0-second cutoff.

Changes

  • Implemented staleness filtering based on the ageInSeconds parameter.
  • Defect Fix: Corrected the legacy Java quirk. Supplying ageInSeconds=0 now explicitly applies a 0-second staleness cutoff.
  • Maintained intended backward compatibility by disabling the filter only when the parameter is completely absent or negative.
  • Added Timestamp to MockVehicleOptions in gtfs_manager_mock.go to allow deterministic testing of vehicle age.
  • Added test coverage for positive, zero, negative, and absent parameter states.

Closes #1103

Summary by CodeRabbit

  • New Features
    • Added an optional ageInSeconds filter to the vehicles-for-agency response, letting results show only vehicles updated within the requested time window.
    • If the parameter is omitted or negative, the full vehicle list is returned as before.

Parse the ageInSeconds query parameter to filter out stale vehicles.
Unlike the legacy Java system, ageInSeconds=0 now strictly filters for
exact-match timestamps instead of silently disabling the filter.
Absent or negative values bypass the filter to maintain expected logic.

Updated MockVehicleOptions to accept custom timestamps and added
tests covering positive, zero, negative, and absent parameter states.
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@3rabiii, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 16 minutes and 27 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 322e8620-0b8b-4320-bb92-3a2d8331aea8

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1d91d and eb8089b.

📒 Files selected for processing (1)
  • internal/restapi/vehicles_for_agency_handler.go
📝 Walkthrough

Walkthrough

Adds ageInSeconds filtering to /vehicles-for-agency, using api.Clock.Now() as the reference time. Extends the GTFS vehicle mock to accept explicit timestamps and adds tests for positive, zero, negative, and omitted query values.

Changes

Vehicle age filtering

Layer / File(s) Summary
Handler age filter
internal/restapi/vehicles_for_agency_handler.go
Reads ageInSeconds, parses nonnegative values, computes a cutoff from api.Clock.Now(), and filters vehicles before pagination.
Timestamped mock vehicles and query coverage
internal/gtfs/gtfs_manager_mock.go, internal/restapi/vehicles_for_agency_handler_test.go
Adds a timestamp override to the GTFS mock and tests positive, zero, negative, and absent ageInSeconds cases with a fixed clock and vehicle ID helper.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant vehiclesForAgencyHandler
  participant api.Clock
  Client->>vehiclesForAgencyHandler: GET /vehicles-for-agency?ageInSeconds=...
  vehiclesForAgencyHandler->>api.Clock: Now()
  vehiclesForAgencyHandler->>vehiclesForAgencyHandler: parse ageInSeconds and filter stale vehicles
  vehiclesForAgencyHandler-->>Client: filtered vehicles response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • OneBusAway/maglev#941: The handler tests in this PR use deterministic clocking and typed response patterns similar to the /vehicles-for-agency test coverage in that change.

Suggested reviewers

  • fletcherw
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing strict ageInSeconds staleness filtering.
Linked Issues check ✅ Passed The handler now honors ageInSeconds, applies a strict zero-second cutoff, and bypasses filtering when absent or negative.
Out of Scope Changes check ✅ Passed The changes stay focused on the endpoint fix and supporting tests/mocks, with no obvious unrelated additions.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/restapi/vehicles_for_agency_handler_test.go (1)

283-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an exact-cutoff inclusion assertion to the zero-second test.

This currently proves that stale vehicles are excluded, but it would still pass if ageInSeconds=0 incorrectly dropped vehicles updated exactly at api.Clock.Now(), which is the key boundary in the PR objective.

Proposed test addition
 func TestVehiclesForAgencyHandler_AgeInSecondsZeroFiltersStrictly(t *testing.T) {
 	api := createTestApiWithClock(t, clock.NewMockClock(ageFilterClock))
 	defer api.Shutdown()
 	t.Cleanup(api.GtfsManager.MockResetRealTimeData)
 
 	trip := mustGetTrip(t, api)
+	exactTS := ageFilterClock
 	staleTS := ageFilterClock.Add(-10 * time.Minute)
+	api.GtfsManager.MockAddVehicleWithOptions("v_exact_zero", trip.ID, trip.RouteID, gtfs.MockVehicleOptions{
+		Timestamp: &exactTS,
+	})
 	api.GtfsManager.MockAddVehicleWithOptions("v_stale_zero", trip.ID, trip.RouteID, gtfs.MockVehicleOptions{
 		Timestamp: &staleTS,
 	})
 
 	params := url.Values{"ageInSeconds": {"0"}}
 	_, model := callAPIHandler[VehiclesForAgencyResponse](t, api, vehiclesForAgencyURL(testdata.Raba.ID, params))
 
+	assert.True(t, vehiclesForAgencyContainsID(model.Data.List, "v_exact_zero"),
+		"ageInSeconds=0 must retain vehicles updated at the exact reference time")
 	assert.False(t, vehiclesForAgencyContainsID(model.Data.List, "v_stale_zero"),
 		"ageInSeconds=0 must apply a strict cutoff and exclude stale vehicles")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/vehicles_for_agency_handler_test.go` around lines 283 - 301,
Update TestVehiclesForAgencyHandler_AgeInSecondsZeroFiltersStrictly in
vehicles_for_agency_handler_test.go to also assert that a vehicle with a
timestamp exactly equal to api.Clock.Now() is included when ageInSeconds is 0.
Use createTestApiWithClock, api.GtfsManager.MockAddVehicleWithOptions, and
vehiclesForAgencyContainsID to add a boundary-case vehicle alongside the stale
one, then verify the exact-cutoff vehicle remains in the response while
v_stale_zero is still excluded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/restapi/vehicles_for_agency_handler.go`:
- Around line 43-45: The age-based cutoff calculation in vehicles filtering can
overflow when converting a large parsed `ageInSeconds` into a `time.Duration`,
which can invert the cutoff and break request filtering. Update the logic in the
handler that parses the age parameter and computes `cutoff` to validate or bound
the parsed value before multiplying by `time.Second`, and only apply the filter
when the conversion is safe; use the existing `strconv.Atoi` parsing path and
the `referenceTime`/`cutoff` computation in `vehiclesForAgencyHandler` as the
place to fix it.

---

Nitpick comments:
In `@internal/restapi/vehicles_for_agency_handler_test.go`:
- Around line 283-301: Update
TestVehiclesForAgencyHandler_AgeInSecondsZeroFiltersStrictly in
vehicles_for_agency_handler_test.go to also assert that a vehicle with a
timestamp exactly equal to api.Clock.Now() is included when ageInSeconds is 0.
Use createTestApiWithClock, api.GtfsManager.MockAddVehicleWithOptions, and
vehiclesForAgencyContainsID to add a boundary-case vehicle alongside the stale
one, then verify the exact-cutoff vehicle remains in the response while
v_stale_zero is still excluded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9001043f-29c4-48e2-9344-12839f58506e

📥 Commits

Reviewing files that changed from the base of the PR and between c298c6e and 6f1d91d.

📒 Files selected for processing (3)
  • internal/gtfs/gtfs_manager_mock.go
  • internal/restapi/vehicles_for_agency_handler.go
  • internal/restapi/vehicles_for_agency_handler_test.go

Comment thread internal/restapi/vehicles_for_agency_handler.go Outdated
3rabiii and others added 2 commits June 27, 2026 00:12
Calculating the time.Duration for the staleness cutoff can overflow if a client sends an excessively large ageInSeconds value. Switch to ParseInt and apply a maximum bound to prevent the duration from wrapping to a negative value.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

@3rabiii 3rabiii requested a review from burma-shave June 26, 2026 21:53
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.

[spec-gap] vehicles-for-agency: ageInSeconds request parameter is not implemented

1 participant