Skip to content

fix(api): stop PUT /v1/vehicles/{id} silently discarding the odometer - #301

Merged
roncodes merged 3 commits into
dev-v0.6.61from
fix/vehicle-update-drops-odometer
Aug 23, 2026
Merged

fix(api): stop PUT /v1/vehicles/{id} silently discarding the odometer#301
roncodes merged 3 commits into
dev-v0.6.61from
fix/vehicle-update-drops-odometer

Conversation

@roncodes

Copy link
Copy Markdown
Member

The problem

odometer is in the Vehicle model's $fillable and nothing in the request
rules forbids it — but VehicleController::vehicleInputFromRequest() builds the
input it applies with $request->only([...]), and that allowlist has no
odometer in it:

return $request->only([
    'status', 'make', 'model', 'year', 'trim', 'type', 'plate_number', 'vin',
    'meta', 'online', 'location', 'altitude', 'heading', 'speed',
    'payload_capacity', ...
]);

So PUT /v1/vehicles/{id} with {"odometer": 211098, "odometer_unit": "km"}
returns 200, hands back a response body that looks entirely correct, and
discards the reading.

Recording mileage is the most common write a driver app makes against a vehicle
— it is the number a fuel report is sanity-checked against. A silent no-op is
the worst of the three available answers: accept it, or reject it, but reporting
success for a field that was dropped leaves the client with no way to find out.

Found while building the Navigator redesign's "My vehicle" screen, which had to
tell drivers that entering an odometer was unavailable. It turned out the
endpoint was there all along and simply threw the value away.

The change

  • odometer and odometer_unit added to the projection.
  • Both validated, not merely accepted. The model casts odometer to an
    integer, so an unchecked string would have been stored as 0 — a vehicle
    reporting that it has never moved, rather than an error. nullable|numeric|min:0
    and nullable|string|max:12. No Rule::in on the unit, because the codebase
    defines no vocabulary for it anywhere and inventing one here could reject what
    the console already writes.

Tests

Two, using the FleetOpsVehicleControllerProbe already in the suite for
protected helpers:

  • the odometer and its unit survive the projection
  • the projection is still an allowlist — adding a field must not turn it
    into "whatever the caller sent", so company_uuid and uuid are still dropped

I could not run them locally. server/tests/VehicleControllerHelperContractsTest.php
fatals on this machine with Trait "Illuminate\Foundation\Auth\Access\AuthorizesRequests" not found
— the whole file, not just the new cases, and identically on a clean checkout of
main at this base commit. It is a missing illuminate/foundation in the local
server_vendor, not anything in this change. CI has the full install. Flagging
it rather than implying a green run I did not see.

Related

Same shape as an issue on the fuel-report and issue list filters, where
driver_uuid is accepted, ignored, and answered with every driver's records.
Separate PR.

`odometer` is fillable on the Vehicle model and unrestricted by the request
rules, but `VehicleController::vehicleInputFromRequest()` builds its input with
`$request->only([...])` and that allowlist had no odometer in it. A caller
sending one therefore received a 200 and a response body that looked correct,
while the reading went nowhere.

Recording mileage is the most common write a driver app makes against a vehicle
— it is what a fuel report is checked against — and a silent no-op is the worst
of the three possible answers. Accept it or reject it; reporting success for a
discarded field leaves the client with no way to tell.

Adds `odometer` and `odometer_unit` to the projection, and validates them rather
than merely accepting them: the model casts odometer to an integer, so an
unchecked string would have been stored as 0, which reads as a vehicle that has
never moved rather than as an error.

Two tests, using the probe already in the suite for protected helpers: one that
the odometer survives the projection, and one that the projection is still an
allowlist — adding a field must not turn it into "whatever the caller sent", so
company_uuid and uuid must still be dropped.
…wns it

The two odometer tests were appended to VehicleControllerHelperContractsTest,
whose probe extends Internal\v1\VehicleController. The helper they exercise,
vehicleInputFromRequest, lives on Api\v1\VehicleController, so the reflection
lookup errored with "method does not exist" and took PHP CI down.

Move them to ApiVehicleControllerContractsTest, which already has a probe
exposing that helper, and pin the new odometer validation rules in
RequestContractsTest alongside the rest of the vehicle request contract.
@roncodes roncodes mentioned this pull request Aug 23, 2026
@roncodes
roncodes changed the base branch from main to dev-v0.6.61 August 23, 2026 03:48
A local symlink into a sibling checkout slipped into the previous commit and
broke `pnpm install` in CI with ENOTDIR. .gitignore's `/node_modules/` has a
trailing slash, so it matches the directory but not a symlink of that name.
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (4e0fc5f) to head (ade56e5).
⚠️ Report is 2 commits behind head on dev-v0.6.61.

Additional details and impacted files
@@               Coverage Diff               @@
##             dev-v0.6.61      #301   +/-   ##
===============================================
  Coverage         100.00%   100.00%           
  Complexity          9815      9815           
===============================================
  Files                523       523           
  Lines              37888     37891    +3     
===============================================
+ Hits               37888     37891    +3     
Flag Coverage Δ
backend 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@roncodes
roncodes merged commit 81dcea2 into dev-v0.6.61 Aug 23, 2026
8 checks passed
@roncodes
roncodes deleted the fix/vehicle-update-drops-odometer branch August 23, 2026 04:06
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.

1 participant