Skip to content

v0.6.59 — backend test coverage, dead-code cleanup and defect fixes - #282

Open
roncodes wants to merge 713 commits into
mainfrom
dev-v0.6.59
Open

v0.6.59 — backend test coverage, dead-code cleanup and defect fixes#282
roncodes wants to merge 713 commits into
mainfrom
dev-v0.6.59

Conversation

@roncodes

@roncodes roncodes commented Aug 2, 2026

Copy link
Copy Markdown
Member

v0.6.59

Release branch consolidating the Fleet-Ops backend test-coverage campaign, the dead-code cleanup it uncovered, and the production defects found along the way.

⚠️ This must not merge to main until fleetbase/core-api 1.6.55 is released. One test on this branch asserts a contract that only holds after that fix — see "Known failing test" below.

What's in this release

Backend test coverage: 79.53% → 99.74%

server/src line coverage went from 79.53% to 99.74%. Tests are organised under server/tests/Unit/... and server/tests/Feature/Http/{Api,Internal}/... rather than adding to the flat root-level sprawl.

Milestone Line coverage Uncovered statements
Campaign start 79.53% ~7,000
#277 + #281 consolidated 99.57% 145
Relation callbacks and push channels 99.62% 131
Observer guards, metric queries, geojson fallbacks 99.67% 114
Shift, simulation, analytics and registry seams 99.69% 104
Relation fallbacks, import defaults and skip guards 99.74% 89

The coverage gate itself is wired into the Composer workflow — composer coverage:baseline writes a Clover report and composer coverage:check enforces --fail-under=100. The gate is intentionally still red; see "Remaining work".

Fifteen production bugs found and fixed

Examining every uncovered line turned up real defects, not just missing tests. Highlights:

  • Place::insertFromMixed() crashed on any plain address string. It called insertFromGeocodingLookup(), which existed nowhere on Place or any ancestor — every such call raised BadMethodCallException. Defined as the insert-side twin of createFromGeocodingLookup, mirroring insertFromGoogleAddress.
  • Invalid coordinates were silently reverse-geocoded at Null Island. GeocoderController::reverse() validated after converting with getPointFromCoordinates(), which is typed : Point and falls back to Point(0, 0) — so the "Invalid coordinates provided." guard never fired. An existing test had codified the buggy behaviour.
  • Place::insertFromCoordinates() never detected empty reverse-geocoding results!$results->count() === 0 compares a bool to an int, so places were silently inserted at 0,0 instead of returning false.
  • Lalamove::getQuotationForMarket() passed the market into the bool $sandbox slot, so market-scoped quotations silently ran against the sandbox host with the market dropped.
  • OrderConfig::default() declared a non-nullable self return while returning first(), fataling for companies without a stored transport config.
  • ServiceRate called Collection::sortByDesc() with no argument, throwing whenever a parcel outsized every fee tier.

Unreachable code removed or repaired

A large share of what looked like "untested" code turned out to be unreachable — branches shadowed by a broader arm above them, guards on values a type declaration forbids, fallbacks after an unconditional assignment. Each was handled deliberately:

  • Reordered where the guard's intent was real, so it now fires with behaviour unchanged (the spatial casts).
  • Deleted only where the shadowing arm was genuinely equivalent.
  • Annotated @codeCoverageIgnore with a reviewable reason where a type or an earlier validate() makes the state impossible.

One case is worth calling out: Casts/MultiPolygon's shadowed arm returned a bare geometry while the arm above it wraps in SpatialExpression. Reordering it verbatim would have silently flipped MultiPolygon writes from wrapped to bare — on the zone/service-area/location write path. It was reordered and matched, and behaviour preservation is evidenced by the spatial suites passing with their original assertions.

Found while covering — not fixed here

Utils::getPointFromMixed() has two fallback arms (Support/Utils.php:275 and :279) that recurse with a bare coordinate pair pulled out of a GeoJSON envelope. The array reader at :296:297 resolves positionally, taking index 0 as latitude and 1 as longitude — the reverse of GeoJSON's [lng, lat]. A pair that reaches either fallback therefore comes back transposed.

server/tests/PointResolutionTest.php asserts the behaviour as it stands, with a comment saying so. Correcting it touches every location write path, so it is deliberately left for its own change rather than folded into a coverage commit.

Known failing test

server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php fails on this branch, deliberately.

Internal\v1\OrderController::nextActivity() wraps Order::findByIdOrFail() in a catch (ModelNotFoundException) that never fires today: core-api's findByIdOrFail() calls a getModelNotFoundException() method that does not exist on Eloquent's builder, so a missing order raises BadMethodCallException, escapes the catch, and surfaces as a 500 instead of a 404. core-api#231 fixes it on dev-v1.6.55.

The test asserts the post-fix contract on purpose — asserting today's BadMethodCallException would codify the defect. It currently fails with exactly Call to undefined method Builder::getModelNotFoundException() and turns green when the release lands.

To keep this branch measurable while that is outstanding, scripts/coverage-file-runner.php gained an opt-in FLEETOPS_COVERAGE_CONTINUE_ON_FAILURE=1: a failing file is recorded rather than aborting the run, the Clover report is still written, and the process still exits non-zero. Without it, the runner exit()s inside its per-file loop — every later file is skipped, no report is written, and the stale clover.xml is left behind.

Remaining work before this ships

Roughly 89 statements are still uncovered, in three groups:

  • Coverable — the bulk. Guard branches, controller arms and import fallbacks reachable by shaping the input, following the same seam and sweep patterns used so far.
  • Provably dead (~19) — shadowed branches, guards on values a type declaration forbids, and method_exists checks on methods that are declared. These get the delete-or-annotate treatment already applied in Remove unreachable backend code and fix two defects it was hiding #281. Two were reclassified during execution: OrderDispatched:151-152 and OrderPing:158-159 sit behind a method_exists($resource, 'toWebhookPayload') check where $resource is an unconditional new OrderResource(...) and Order::toWebhookPayload() is declared — so the elseif can never run.
  • Environment-blocked (~4) — the extension_loaded('geos') branch, the "core-api absent" throws, and ProofController::createSignatureFile, whose File creation resolves a disk, a url and app()->environment() and so needs a real Application rather than the container the harness builds.

Then wire composer coverage:check into .github/workflows/server.yml, and pull core-api 1.6.55 to confirm the gated test turns green.

Scope note on the test suite

Worth being explicit, since a coverage percentage invites over-reading: this suite runs on in-memory SQLite with eval'd function shims, container stand-ins and hand-registered spatial UDFs. It is good at proving branch logic and contracts, and poor at proving real-MySQL behaviour — the spatial write paths especially, where a fixture's prepareBindings override compensates for a real cast divergence. A green run means "the branches behave as described", not "the feature works against a real database". Manual verification against MySQL is still warranted for zones, service areas, and location save/update.

roncodes and others added 30 commits July 28, 2026 18:58
Adds
server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php
covering the AFAQY provider transport with faked HTTP: authentication
resolving fresh tokens with missing-credential, failed-login and
missing-token errors, authenticated posts refreshing rejected tokens and
retrying once, immediate failures when refresh credentials are absent,
non-auth failure propagation with provider error context, connection
timeouts surfacing transport exceptions, and the byte-count, ignition,
fuel-level and sensor identity/name extraction helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds
server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php
covering the Lalamove integration with a mocked Guzzle client:
preliminary-stop and payload quote requests resolving markets from stop
countries, persisting service quotes with generated uuids and
base/vat quote items through the model event dispatcher, and the full
createOrderFromServiceQuote flow resolving the sender from the first
waypoint, recipients from remaining stops with parsed phones, POD flags
from the request, and company/service-quote metadata posted to the
orders endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php
covering the internal ServiceQuoteController preliminary flow against
SQLite: single-service quotes recalculating distance through the
calculate matrix provider and persisting quotes with items, best-quote
selection for single requests across all servicable rates, and the
integrated-vendor branches returning empty single and list payloads when
the vendor cannot be resolved in both the payload-backed and preliminary
query paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php
covering the API DriverController against SQLite with a stand-in
SwitchOrganizationRequest, unblocking the previously fatal core
form-request path: successful organization switches validating company
membership, moving the user session, issuing a sanctum token for the
target driver profile and returning the organization payload, the
driver-not-found 404 branch, and the geofence crossing processor
upserting entry states with triggered events, skipping untriggered
entries, and closing exited states with dwell duration calculations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds
server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php
covering the internal DriverController createRecord unique-conflict branch
against SQLite with a contract-implementing failing validator: phone
conflicts adopting an existing organization member by creating a driver
profile with the default location and skipping company assignment for
members, email conflicts returning the already-existing driver profile,
and non phone/email conflicts falling through to the validation error
response seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php covering
Maintenance and WorkOrder import-row creation against SQLite:
maintainable and target resolution by plate number with the equipment
name fallback, driver performer and vendor assignee resolution, persisted
imports, start/complete lifecycle guards for non-eligible statuses,
duration efficiency null fallback without estimated hours, work-order
code generation on create, and line-item normalization from json strings
and scalar rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php
covering the API PlaceController helper seams against SQLite — uuid and
value lookups, model class resolution, or-value fallbacks, first-or-new
places, find-or-fail, geocoding-backed creation through the empty
geocoder, search options and coordinate parsing with the search endpoint —
plus the API ServiceQuoteController preliminary flow resolving pickup
places by public id and dropoffs from mixed arrays into single-service
quotes with persisted items, the payload-backed integrated-vendor branch
returning empty collections for missing vendors, and the preliminary
missing-vendor unset-quote error seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php
covering the fleetops:simulate-geofence-events command against SQLite:
event parsing for sequences, comma lists and invalid values, subject and
geofence resolution across driver/vehicle and zone/service-area public
ids and uuids, state table and column mapping, the full simulation loop
marking inside and outside states with dwell math and dispatching the
entered/dwelled/exited event sequence, one-second sleep pacing, and the
failure branches for invalid events and unresolvable subjects or
geofences. The Zone location accessor requires the GEOS extension, so the
full-run probe hydrates a coordinate-stubbed zone subclass while the real
resolvers are covered by reflection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php
covering the internal OrderController capturePhoto endpoint against
SQLite, unblocking the previously documented validation-closure limit
with a validator fake that executes the closure photo rules for real:
base64 photo captures persisting proofs with stored files through a
filesystem-contract disk fake, waypoint subject resolution for scoped
captures, invalid base64 strings failing the closure rule with 422
responses, empty photo payloads failing the required rule, and unknown
orders returning errors. Resource lifecycle serialization needs
app()->environment(), so the boot swaps in an environment-aware container
subclass carrying the harness container state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php
covering the internal OrderController importFromFiles endpoint against
SQLite with a faked ipdata lookup, excel reader and geocoder: spreadsheet
rows importing places through createFromImportRow with pipe-delimited
entity items attached to their destinations, empty-row skips, invalid
file-type rejection for non-spreadsheet uploads, and unreadable
spreadsheet errors from the reader seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php
with waypoint service-stop coverage: multi-waypoint orders gating waypoint
activity updates behind the started state with a 422, completing the
current stop and advancing the payload's current waypoint, completing the
order itself once the final stop is exhausted on a waypoint-only route,
and next-activity resolution for the current and explicitly scoped
waypoint stops on started orders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php
with the API OrderController activity residuals: lifecycle started and
completed activities updating classic orders through to completion with
driver release, waypoint-route orders auto-starting from created status
and advancing service stops through repeated completion activities,
current and waypoint-scoped next-activity resolution, and completeOrder
gating on incomplete waypoints before completing with the resolved
activity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Unit/Support/IntegratedVendorsResolverTest.php covering
the integrated vendor registry and resolver object against SQLite:
resolver lookup with magic getters, dynamic get/set calls and logo urls,
bridge instantiation with resolved credential params, service and country
bridge instances with their static listings, callback dispatching through
configured bridge methods with resolved webhook params, and the static
service-types accessor — plus the HasTrackingNumber trait generating
tracking numbers on waypoint insert, proof resolution by public id and
instance, and activity template string fast paths and placeholder
resolution for orders and waypoints.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php
with an all-types dispatch test: every registered search type arm from
orders through order-configs executes against empty per-type tables with
the admin permission bypass and the per-type limit division, covering the
full match expression in searchType.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php
covering the fleetops:replay-vehicle-locations command against SQLite:
missing-file, invalid-speed, unparseable-json and empty-payload failures,
the no-match filter warning, the full replay loop sending events per
vehicle channel with recorded whole-second and fractional sleep pacing,
fixed sleep overrides, unknown-vehicle skips, socket send failures
counted into the failure exit code, and the file, vehicle, timer and
sleep helper seams.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php
covering the internal DeviceController against SQLite: spreadsheet
exports through the excel download seam with a stand-in export request,
query-record filters for attached, unattached and unknown attachment
states, vehicle scoping by attachable uuid and resolved public-id uuids,
and the device and vehicle resolver seams for uuid, public-id and missing
inputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php
covering the API ServiceAreaController against SQLite: creating service
areas with multipolygon borders derived from latitude/longitude pairs
with parent public-id resolution and from mixed location inputs, plus the
helper seams for border construction, service-area uuid lookup, point
parsing, record persistence and retrieval, resource and deleted-resource
wrappers, json responses, and create-failure logging through a namespaced
logger shim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php
with direct coverage of the ResolvesOrderServiceStops trait through an
OrderController probe: waypoint-stop activity updates inserting tracking
activities, syncing tracking-number statuses, firing waypoint and entity
change events for in-progress activities and completion events for
completing ones, endpoint-stop activity updates creating pickup tracking
numbers on the payload columns with started statuses, and empty
next-activity resolution for unknown current status codes. The fixture's
spatial point function now emits packed WKB so stored activity locations
rehydrate through the spatial casts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
roncodes and others added 17 commits August 3, 2026 16:26
Both quote controllers branch on `single`, and every existing test passed it,
so the arms that wrap a quote in a collection were never entered — on either
the named-service path or the all-rates path, and in both the API and internal
controllers.

The API probe also swallowed the callback that getServicableServiceRates()
hands a query builder, which left the company scoping the controller applies
untested. It now runs the callback against a recorder and asserts the
constraint, which is why the existing collection test grew a session: it
reaches code that reads one now.

Line coverage 99.74% -> 99.76% (34006/34088).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every stripe fixture builds an unsaved quote, so the arm that persists the
product id rather than staging it in memory was never taken. Asking for the
price before the product covers the provisioning path the same fixtures skip
by fetching the product first.

previousActivity() resolved its context off the config exactly like the
forward helpers, but every test passed one explicitly. The assertion pins the
current activity in the same chain because the no-activity fallback returns an
empty collection too, so the count alone would not say which arm ran.

ServiceQuote:214 stays uncovered and is not reachable: it returns null when
Payment::getStripeClient() is falsy, but that method is nullable in signature
only and always returns a new StripeClient.

Line coverage 99.76% -> 99.77% (34010/34088).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ompany

The entity label action reached for a new internal `labels/{id}` route wired
across into the public `Api\v1\LabelController`. The internal namespace already
exposes `orders/label/{id}` via `Internal\v1\OrderController@label`, which
already resolves `type=entity` through `findEntityLabelSubject()`, so no new
backend route is needed. `$type` also defaults to `strtok($publicId, '_')`, so
an `entity_*` public id resolves on its own and the query param can go.

- drop the added internal `labels` route group
- call `orders/label/{public_id}?format=base64`, matching `viewWaypointLabel`
- reuse `modals/order-label` instead of cloning it, as the waypoint action does,
  with an `@options.subject` fallback so the object alt still resolves
- fix the `Failed to load entity label.s` typo and add the two new keys to the
  six other locales that already carry the waypoint equivalents

Also scopes label subject resolution to the session company. The lookups matched
on identifier alone, so any authenticated user could render a label for any
order, waypoint or entity in another organization by supplying its public id.
The identifier match is grouped in a closure — appending the company constraint
to the existing chain would read as `public_id = ? OR (uuid = ? AND company_uuid
= ?)` and still leak. Resolution fails closed when there is no company session.
Applied to both the internal and public API paths, with regression coverage for
the foreign-company, precedence and no-session cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(labels): add view label action for individual entities
Read GeoJSON fallback coordinates in GeoJSON order
`GET /v1/vehicles?vendor=...` could never return results. Two constraints
were ANDed onto the same request parameter: `queryVehicles()` matched the
vendor by `public_id`, while `VehicleFilter::vendor()` matched the same
relation by `uuid`. `searchBuilder()` applies the filter before the
controller callback, so both landed on one builder and a vendor's `uuid`
and `public_id` are never equal in production.

Drop the redundant `whereHas` from the controller and widen the filter to
resolve the identifier the way `PartFilter::vendor` does: public id or
internal id everywhere, uuid additionally on internal routes so the
management console keeps working (it sends the uuid as the record id).

The pipeline test previously passed no `vendor` parameter at all, so it
never exercised the conflict; it now covers the public id, internal id,
unknown-identifier and internal-route uuid cases against real rows. The
filter contract test moves to the blank-vendor branch, matching how
`PartFilter::vendor` is asserted there, since the resolved branch needs a
database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fifteen statements across eleven controller and filter fixtures, all reached
by shaping the request or the collaborator rather than by stubbing the seam
under test:

- `PartFilter` 42 — vendor lookup by raw uuid on an internal route
- `OrchestrationController` 96/99 — the workbench `with()` constraints. The
  query fake only invoked top-level closure arguments, so the eager-load map's
  nested closures never ran; it now descends one level into array arguments
- `MaintenanceController` 154/155 — a reader throwing mid-import
- `TelematicController` 255 — a failed connection test, including the
  sensitive-message scrub
- `HubController` 212 — the create-fleets action, with every other action
  condition unmet so the single result can only come from that arm
- `PlaceController` 124 — address-only creation filling from the geocoded result
- `Api\v1\OrderController` 1223 — no resolvable order config
- `Internal\v1\OrderController` 230/231 — an unexpected collaborator failure,
  and 378 — an import row with nothing that resolves to an address
- `Api\v1\DriverController` 555 — the SMS branch of the phone login, reached by
  binding a `twilio` fake (nothing binds it in the harness, so every phone
  login had been falling through to email)
- `Api\v1\VehicleController` 415-417 — the vendor query hook

The vehicle vendor test documents a defect it had to work around: `VehicleFilter::vendor`
binds the same `vendor` request parameter and matches on `uuid` while the
controller hook matches on `public_id`. Both constraints are ANDed, so with a
real uuid the parameter can never match anything. Tracked separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- `Place` 814 — an array carrying an `address` key routes through the
  geocoding lookup rather than the shared-place or direct-insert arms
- `Place` 1044 — a single-column import whose geocoder answers with no
  addresses (rather than rejecting the request) falls through to mixed creation
- `Payload` 326 — an entity with only a `waypoint` key resolves its destination
  through `findDestinationFromKey`
- `Payload` 612 — a console place search hands back a uuid no place carries, so
  the created place keeps it as `meta.search_uuid`
- `Payload` 1159 — a console destination key matches no place uuid and no
  search uuid, resolving only through the entity-correction fallback

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- `FuelProviderService` 341/345 — the provider-id and structure-number fields
  are special-cased ahead of the generic field map; 408 — station coordinates
  become the fuel report's location instead of the (0, 0) placeholder
- `TelematicService` 202 — a snapshot with no events list normalizes the
  payload itself as the single event; 307 — a sensor reading carrying
  coordinates keeps them; 382 — a failing credential validator raises
- `OperationalQueryCapability` 219 — a driver whose stored location is too
  short to rehydrate as WKB is skipped before the geofence lookups
- `OrderInsightsCapability` 93 — the real order query seam, which behaviour
  tests replace with a fake
- `VroomOrchestrationEngine` 141 — a single-stop order goes out as a job, so
  the empty shipments key is dropped
- `SendMaintenanceReminders` 65 — a schedule with a non-null but empty offsets
  array is skipped
- `Flow\Activity` 107 — the fireEvents loop body

The harness stand-in for `Illuminate\Validation\ValidationException` only
accepted a message string, but Laravel's real constructor takes the failing
validator — which is how `TelematicService::validateCredentials` builds it. The
stand-in now accepts either and exposes the validator's errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- `TrackingIntelligenceService` 225 — with nothing on the order itself marking
  it started, a loaded tracking status collection is the only remaining answer
- `Lalamove` 565/587 — a network-cart origin arrives as an array of store
  locations, which becomes the waypoint list, and each entry resolves its
  place through the wrapping `place` key

Four lines from this group turned out unreachable and are left for the
dead-code pass: `ServiceRate` 1218 (`calculateMultiZoneDistances` already
guarantees every entry's rule is a `ServiceRateFee`) and 1281 (`$places` is
filtered to `Place` instances, and `getLngLatFromPlace` only returns null for
non-places); `Order` 1739 (`getDrivingDistanceAndTime` is typed `: DistanceMatrix`
and every producer casts both fields to float); `ResolvesOrderServiceStops` 385
(`insertGetUuid` only returns false when an insert returns false, which Laravel's
forced `PDO::ERRMODE_EXCEPTION` never does) and 419 (the tracking number is
always a fresh query result, so its `status` relation is never loaded).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`GeotabProvider` 234, `AfaqyProvider` 343 and `SafeeProvider` 417 go in their
own file: `Http::fake()` state leaks between tests in the shared harness, which
is why `TelematicsHardeningTest.php:449` carries a skip.

- `GeotabProvider` 234 — the real post seam, which behaviour tests replace with
  a canned response queue
- `AfaqyProvider` 343 — a token rejected when the provider *could* refresh but
  the retry has already been spent, which reports differently from the
  no-credentials case
- `SafeeProvider` 417 — a successful auth response carrying no access token
- `GeocoderController` 112 — the place builder seam

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Place` 203 and `Vehicle` 528 were previously written off as unreachable.
`File::getUrlAttribute` only needs `app()->environment()` on the `disk === 'local'`
arm, so a row seeded with a non-local disk returns its url directly — through
the existing filesystem fake for places, and through a real `FilesystemAdapter`
over `LocalFilesystemAdapter` for vehicles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Contact` 385, 391 and 534. The blocker was the fixture never installing an
event dispatcher, so `User`'s uuid hook never ran and `User::create()` came
back with a null uuid — which silently made the existing assertions in this
file compare null to null. Memoising a dispatcher (never replacing one that
already exists, since model hooks bind to whichever instance was present when
the class booted) fixes that, and brings the observers along with it, hence the
`responsecache` binding, the activitylog config, the `humanize` macro and the
`slug` column.

- 385 — a customer contact with no matching identity gets a provisioned user
  and the Fleet-Ops Customer role, which attaches to the company-user record
  the user proxies authorization through
- 391 — with the update flag the link is written back to the contact row
  rather than only being set in memory
- 534 — an already-assigned user is checked ahead of the identity lookup

All twelve pre-existing tests in the file were re-verified against the new boot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves the conflict in VehicleControllerTrackingTest. dev-v0.6.59 landed
a coverage pass on the same test that worked around this PR's bug: it gave
the fixture vendor the same value for `uuid` and `public_id` so the two
conflicting constraints could agree, and left a NOTE describing the clash
as tracked separately.

This PR removes the clash, so the workaround and its NOTE go with it. The
resolution keeps this branch's assertions — public id, internal id,
unknown identifier, uuid rejected on public routes and accepted on
internal ones — over real uuids, and adopts the incoming multi-row insert
style for the fixtures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same treatment as #281: delete where the removal is genuinely equivalent,
annotate with a reviewable reason otherwise.

Deleted — the guard could not change behaviour:
- `Api\v1\OrderController` 276, a `Str::isUuid()` re-check on a value the seam
  above is typed to return as a `Contact`
- `Internal\v1\OrderController` 1493, `method_exists($config, 'activities')`
  where `OrderConfig::activities()` is declared
- `Internal\v1\TelematicController` 100, a null check on a `resolve()` typed
  `: TelematicProviderInterface` that throws instead of returning null
- `LalamoveServiceType` 129/133, both `__call` forwards — every method on the
  class is public, so `__call` only fires for names it does not define
- `ServiceArea` 187 and `Zone` 239, the third and fourth copies of a
  ring-closing guard `Utils::coordsToCircle()` already performs
- `OrderDispatched` 151/152 and `OrderPing` 158/159, the `method_exists`
  fallback arms behind a check on a method `OrderResource` declares
- `Api\v1\PurchaseRateController` 212, the not-an-Order path out of
  `Order::create()`

Annotated with `@codeCoverageIgnore` and the reason:
- `Api\v1\OrderController` 743-756, the driver-nearby branch. Every driver with
  a location is consumed by the coordinates branch above; what reaches here is
  a driver without one, and the distance query then raises on the null point.
- `Api\v1\DriverController` 663/734, `Internal\v1\OrderController` 1032,
  `ServiceQuote` 214, `Order` 936/1739, `ServiceRate` 1218/1281,
  `ResolvesOrderServiceStops` 385/419
- environment-blocked: `FleetOpsServiceProvider` 13/198,
  `NotificationServiceProvider` 10, `ProofController` 131

Also finishes the ValidationException stand-in started in the coverage work: it
now carries the response Laravel passes as the second constructor argument. Two
tests asserted `toThrow(TypeError::class)` — an artifact of the stand-in being
unable to accept a validator — and now assert the validation errors and the 422
response instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix vendor filtering on the public vehicles endpoint
Calls the reusable api-contract workflow in fleetbase/fleetbase to boot a
full stack and run this module's Postman collection against the live API.
No-ops until the org POSTMAN_API_KEY secret is set. Pinned to @dev-v0.7.53
until that branch merges to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread .github/workflows/postman.yml Fixed
roncodes and others added 9 commits August 3, 2026 20:27
…ntain permissions'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
The pest runner created a persistent `vendor -> server_vendor` symlink
(Pest #920 workaround for its hardcoded ../../../vendor/autoload.php) but
never removed it. That leftover symlink collides with Ember's addon
`vendor/` convention when this package is dev-linked into the console,
breaking the Ember build on case-insensitive filesystems.

Create the symlink only for the duration of the run and remove it via a
shutdown hook, so it never persists outside a Pest run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GeofenceController@inventory: the driver-states query selected `d.name`,
  but the drivers table has no `name` column (it lives on the related user).
  Join `users` and select `u.name` for subject_name/driver_name.
- DriverController@registerDevice: the method required `string $id`, but the
  `register-device` route (no id) and the internal delegation both call it
  without one, causing an ArgumentCountError (500). Make `$id` optional and
  resolve the driver from the authenticated user when it is absent.
- Device model: `devices.last_position` is NOT NULL (for its spatial index)
  with no default, so every insert failed ("Field 'last_position' doesn't
  have a default value"). Default it to POINT(0,0) on create.

Verified against a running instance: /v1/geofences/inventory -> 200,
/v1/drivers/register-device -> 404 (no longer 500), /v1/devices -> 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…test suite

Keep the geofence inventory fix (d.name -> u.name via users join) but revert
the DriverController::registerDevice signature change and the Device
last_position boot default: both add code paths that the strict 100% coverage
suite requires new tests for, and the registerDevice signature change broke
existing positional-call contract tests. These two verified fixes will land in
a dedicated PR with matching test coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two public-API 500s surfaced by the Postman contract:

- DriverController@registerDevice required `string $id`, but the
  `…/register-device` route (no id) and the internal delegation both call
  it without one -> ArgumentCountError (500). Make `$id` optional and add
  a `currentDriver()` resolver (driver from the authenticated user) used
  when no id is supplied; the internal delegation now passes (null, $request).
- Device: `devices.last_position` is NOT NULL (spatial index) with no
  default, so every insert failed. Default it to POINT(0,0) on create.

Tests: extend the driver controller contract probe with a `currentDriver`
override and cover the no-id register-device happy path and missing-driver
branch.

Verified against a running instance: /v1/drivers/register-device -> 404
(no longer 500), /v1/devices -> 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vice-defaults

fix(api): register device without an id + default device last_position
core-api v1.6.55 released, so `findByIdOrFail` now throws a real
ModelNotFoundException and OrderControllerUpstreamNotFoundTest passes,
covering Internal/v1/OrderController 887-888. That unblocked the suite
and exposed the tail of the run, which had never executed in CI:
pest-file-runner exits at the first failing file, so only one failure
was ever visible per run.

Realign test doubles that had drifted from the signatures they stand in
for:

- FleetOpsApiDriverDelegationFake::registerDevice kept a one-arg
  signature after the public controller took (?string $id, ?Request);
  the delegation assertion now pins the null id.
- FleetOpsGeofenceTableQueryFake::leftJoin only accepted the closure
  form, but GeofenceController now joins users by column.
- FleetOpsRouteRecorder had no middleware(), which routes.php began
  calling once the internal group stopped being conditional.
- CustomerContactIdentitySafetyTest asserted on addon/components/
  customer/form.hbs; the user-account panel was removed deliberately
  (selecting a user is not required to create a customer), and a
  backend test should not gate on frontend markup.

Cover the three remaining statements, each slice-verified rather than
inferred from a passing assertion:

- Api/v1/DriverController::currentDriver — the contract probe overrides
  this seam, so the real body never ran; reflect-invoke it against a
  real fixture plus the not-found branch.
- Internal/v1/DriverController assignCompany for a non-member, which
  the adoption test's own docblock claimed but never exercised.
- Device::boot's creating hook defaulting last_position to POINT(0,0);
  the existing device fixtures boot without an event dispatcher, so the
  hook was never registered.

Two infra fixes that would have blocked CI regardless of coverage:

- composer process-timeout 0: the 300s default killed
  `composer coverage:baseline` mid-run. The step had never actually
  executed in CI because the job always failed earlier, so this was
  latent.
- pest-runner applies a 512M memory floor instead of inheriting the
  ambient php.ini. fleetbase/countries loads a large JSON5 dataset that
  pushes past a stock 128M, so the suite passed or failed on local
  configuration. Overridable via FLEETOPS_TEST_MEMORY_LIMIT; never
  lowers an already-higher or unlimited value.

Baseline is now 100.00% on all three metrics (34,053/34,053 statements,
4,355/4,355 methods, 521/521 classes); composer test:unit, test:lint and
the --fail-under=100 gate all exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@df0ed09). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff            @@
##             main      #282   +/-   ##
========================================
  Coverage        ?   100.00%           
  Complexity      ?      9766           
========================================
  Files           ?       521           
  Lines           ?     37762           
  Branches        ?         0           
========================================
  Hits            ?     37762           
  Misses          ?         0           
  Partials        ?         0           
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 and others added 2 commits August 5, 2026 01:23
TelematicsHardeningTest and CustomerContactIdentitySafetyTest read Ember
templates and components through file_get_contents(__DIR__ . '/../../addon/...')
and asserted on their source text. That put UI edits on the backend CI critical
path — a deleted panel in customer/form.hbs broke the PHP suite, which skipped
the coverage gate and therefore the Codecov upload. The assertions also matched
source strings rather than behavior, so they passed on a match inside a comment
and failed on a harmless rename, while executing no server/src code.

All 15 addon/ reads are gone. Each assertion is re-expressed in the Ember suite
against behavior:

- cell/telematic-status, connectivity/telematics/details: active and connected
  both surface as "Connected"
- telematic/details: webhook URL is derived from public_id and is null when only
  the Ember uuid exists; last_sync_job_id and last_sync_error reach the UI
- telematic/form, telematic/settings: endpoint fields are partitioned into the
  advanced section and fall back to provider default_value
- device/details, device/panel-header: last-seen timestamp formatting and
  connection state taking precedence over the raw online flag
- customer/form: welcome email is opt-in, gated on isNew plus the customer
  portal extension, and writes meta.customer_portal.send_welcome_email without
  dropping sibling meta; the user selector queries is_customer

The backend assertions in both PHP files are untouched.

Two build fixes were needed for the Ember suite to load this addon at all:
@ember/legacy-built-in-components is an optional peer of ember-engines, so pnpm
skips it and vendor.js dies on a missing module; and a lazy engine keeps its
modules out of the dummy app, so every dummy/components/* import failed to
resolve. Eager loading is scoped to `ember test` run from this package, leaving
host apps — and `ember build` — on the lazy path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

3 participants