@fleetbase/fleetops-engine 0.6.58 → 0.6.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/RELEASE.md +36 -0
- package/addon/components/customer/form.hbs +0 -49
- package/addon/components/entity/card.hbs +1 -0
- package/addon/components/modals/order-label.hbs +1 -1
- package/addon/components/place/form.hbs +1 -1
- package/addon/components/vehicle/details/schedules.hbs +2 -2
- package/addon/components/vehicle/details/schedules.js +7 -1
- package/addon/controllers/maintenance/schedules/index/edit.js +10 -1
- package/addon/services/entity-actions.js +32 -0
- package/addon/services/vehicle-actions.js +4 -2
- package/addon/styles/fleetops-engine.css +0 -1
- package/composer.json +8 -3
- package/extension.json +1 -1
- package/index.js +7 -1
- package/package.json +2 -1
- package/scripts/coverage-file-runner.php +240 -0
- package/scripts/coverage-runner.php +2 -1
- package/scripts/coverage-summary.php +41 -2
- package/scripts/pest-bootstrap.php +237 -3
- package/scripts/pest-file-runner.php +13 -1
- package/scripts/pest-runner.php +57 -2
- package/server/config/fleetops.php +44 -0
- package/server/config/geocoder.php +1 -1
- package/server/migrations/2026_08_08_000001_make_sku_and_provider_transaction_unique_indexes_soft_delete_aware.php +152 -0
- package/server/resources/views/labels/entity-label.php +4 -4
- package/server/src/Casts/MultiPolygon.php +8 -3
- package/server/src/Casts/OrderConfigEntities.php +10 -3
- package/server/src/Casts/Point.php +10 -6
- package/server/src/Casts/Polygon.php +5 -2
- package/server/src/Console/Commands/AssignCustomerRoles.php +28 -6
- package/server/src/Console/Commands/AssignDriverRoles.php +36 -6
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +6 -1
- package/server/src/Console/Commands/DispatchAdhocOrders.php +12 -2
- package/server/src/Console/Commands/FixCustomerCompanies.php +46 -11
- package/server/src/Console/Commands/FixDriverCompanies.php +18 -3
- package/server/src/Console/Commands/FixLegacyOrderConfigs.php +31 -6
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +94 -42
- package/server/src/Console/Commands/PurgeUnpurchasedServiceQuotes.php +42 -12
- package/server/src/Console/Commands/ReplayVehicleLocations.php +120 -55
- package/server/src/Console/Commands/SendDriverNotification.php +25 -4
- package/server/src/Console/Commands/SendMaintenanceReminders.php +43 -23
- package/server/src/Console/Commands/SimulateOrderRouteNavigation.php +36 -6
- package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +6 -1
- package/server/src/Events/DriverLocationChanged.php +1 -1
- package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
- package/server/src/Events/EntityActivityChanged.php +1 -1
- package/server/src/Events/EntityCompleted.php +1 -1
- package/server/src/Events/EntityDriverAssigned.php +1 -1
- package/server/src/Events/FuelProviderTransactionImported.php +1 -1
- package/server/src/Events/FuelProviderTransactionMatched.php +1 -1
- package/server/src/Events/FuelProviderTransactionUnmatched.php +1 -1
- package/server/src/Events/FuelReportCreatedFromProvider.php +1 -1
- package/server/src/Events/GeofenceDwelled.php +1 -1
- package/server/src/Events/GeofenceEntered.php +1 -1
- package/server/src/Events/GeofenceExited.php +1 -1
- package/server/src/Events/VehicleLocationChanged.php +1 -1
- package/server/src/Events/WaypointActivityChanged.php +1 -1
- package/server/src/Events/WaypointCompleted.php +1 -1
- package/server/src/Exceptions/IntegratedVendorException.php +2 -2
- package/server/src/Exceptions/TelematicRateLimitExceededException.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ContactController.php +89 -22
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +346 -101
- package/server/src/Http/Controllers/Api/v1/DeviceController.php +48 -14
- package/server/src/Http/Controllers/Api/v1/DriverController.php +224 -64
- package/server/src/Http/Controllers/Api/v1/EntityController.php +112 -66
- package/server/src/Http/Controllers/Api/v1/EquipmentController.php +40 -10
- package/server/src/Http/Controllers/Api/v1/FleetController.php +54 -14
- package/server/src/Http/Controllers/Api/v1/FuelReportController.php +54 -14
- package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +36 -11
- package/server/src/Http/Controllers/Api/v1/GeofenceController.php +73 -23
- package/server/src/Http/Controllers/Api/v1/IssueController.php +54 -14
- package/server/src/Http/Controllers/Api/v1/LabelController.php +61 -22
- package/server/src/Http/Controllers/Api/v1/NavigatorController.php +32 -15
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +35 -5
- package/server/src/Http/Controllers/Api/v1/OrderController.php +388 -203
- package/server/src/Http/Controllers/Api/v1/PartController.php +40 -10
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +90 -30
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +188 -91
- package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +100 -36
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +96 -26
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +169 -81
- package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +104 -75
- package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +92 -14
- package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +66 -16
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +121 -67
- package/server/src/Http/Controllers/Api/v1/VendorController.php +54 -14
- package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +47 -12
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +84 -24
- package/server/src/Http/Controllers/Concerns/ResolvesReviewAccountBypass.php +62 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +15 -5
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +113 -28
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +36 -11
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +9 -4
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +168 -70
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +18 -3
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +137 -60
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +24 -9
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +18 -3
- package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +26 -8
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +12 -2
- package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +27 -6
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +82 -29
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +34 -26
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +141 -97
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +28 -8
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +21 -12
- package/server/src/Http/Controllers/Internal/v1/MorphController.php +90 -18
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +88 -35
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +149 -39
- package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +54 -18
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +298 -86
- package/server/src/Http/Controllers/Internal/v1/PartController.php +18 -3
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +46 -14
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +31 -5
- package/server/src/Http/Controllers/Internal/v1/ProofController.php +88 -48
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +6 -1
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +6 -1
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +255 -105
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +88 -35
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -4
- package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +42 -14
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +103 -33
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +42 -12
- package/server/src/Http/Filter/DriverFilter.php +2 -2
- package/server/src/Http/Filter/MaintenanceScheduleFilter.php +40 -0
- package/server/src/Http/Filter/OrderFilter.php +1 -1
- package/server/src/Http/Filter/VehicleFilter.php +21 -7
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +12 -2
- package/server/src/Http/Requests/BulkDispatchRequest.php +3 -9
- package/server/src/Http/Requests/CancelOrderRequest.php +2 -6
- package/server/src/Http/Requests/CreateContactRequest.php +2 -6
- package/server/src/Http/Requests/CreateCustomerRequest.php +9 -1
- package/server/src/Http/Requests/CreateDeviceRequest.php +2 -2
- package/server/src/Http/Requests/CreateDriverRequest.php +3 -9
- package/server/src/Http/Requests/CreateEntityRequest.php +2 -6
- package/server/src/Http/Requests/CreateEquipmentRequest.php +2 -2
- package/server/src/Http/Requests/CreateFleetRequest.php +2 -6
- package/server/src/Http/Requests/CreateFuelReportRequest.php +16 -9
- package/server/src/Http/Requests/CreateFuelTransactionRequest.php +66 -3
- package/server/src/Http/Requests/CreateIssueRequest.php +2 -6
- package/server/src/Http/Requests/CreateOrderRequest.php +7 -8
- package/server/src/Http/Requests/CreatePartRequest.php +41 -3
- package/server/src/Http/Requests/CreatePayloadRequest.php +7 -9
- package/server/src/Http/Requests/CreatePlaceRequest.php +2 -6
- package/server/src/Http/Requests/CreatePurchaseRateRequest.php +2 -6
- package/server/src/Http/Requests/CreateSensorRequest.php +2 -2
- package/server/src/Http/Requests/CreateServiceAreaRequest.php +2 -6
- package/server/src/Http/Requests/CreateServiceQuoteRequest.php +2 -6
- package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -6
- package/server/src/Http/Requests/CreateTrackingNumberRequest.php +2 -6
- package/server/src/Http/Requests/CreateTrackingStatusRequest.php +2 -6
- package/server/src/Http/Requests/CreateVehicleRequest.php +2 -6
- package/server/src/Http/Requests/CreateVendorRequest.php +2 -6
- package/server/src/Http/Requests/CreateWorkOrderRequest.php +2 -2
- package/server/src/Http/Requests/CreateZoneRequest.php +2 -6
- package/server/src/Http/Requests/DecodeTrackingNumberQR.php +2 -6
- package/server/src/Http/Requests/DriverSimulationRequest.php +2 -6
- package/server/src/Http/Requests/Internal/AssignOrderRequest.php +3 -7
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +4 -12
- package/server/src/Http/Requests/Internal/CreateOrderConfigRequest.php +2 -6
- package/server/src/Http/Requests/Internal/CreateOrderRequest.php +7 -8
- package/server/src/Http/Requests/Internal/FleetActionRequest.php +18 -12
- package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +6 -3
- package/server/src/Http/Requests/QueryServiceQuotesRequest.php +2 -6
- package/server/src/Http/Requests/ScheduleOrderRequest.php +2 -6
- package/server/src/Http/Requests/UpdateIssueRequest.php +1 -3
- package/server/src/Http/Resources/v1/Customer.php +2 -2
- package/server/src/Http/Resources/v1/Maintenance.php +3 -4
- package/server/src/Http/Resources/v1/MaintenanceSchedule.php +3 -4
- package/server/src/Http/Resources/v1/Order.php +3 -7
- package/server/src/Http/Resources/v1/PurchaseRate.php +4 -6
- package/server/src/Http/Resources/v1/TrackingNumber.php +33 -0
- package/server/src/Http/Resources/v1/TrackingStatus.php +4 -6
- package/server/src/Http/Resources/v1/WorkOrder.php +3 -4
- package/server/src/Imports/ContactImport.php +10 -1
- package/server/src/Imports/DriverImport.php +10 -1
- package/server/src/Imports/EquipmentImport.php +6 -1
- package/server/src/Imports/FleetImport.php +10 -1
- package/server/src/Imports/FuelReportImport.php +10 -1
- package/server/src/Imports/IssueImport.php +10 -1
- package/server/src/Imports/MaintenanceImport.php +6 -1
- package/server/src/Imports/MaintenanceScheduleImport.php +6 -1
- package/server/src/Imports/PartImport.php +6 -1
- package/server/src/Imports/PlaceImport.php +10 -1
- package/server/src/Imports/VehicleImport.php +10 -1
- package/server/src/Imports/VendorImport.php +10 -1
- package/server/src/Imports/WorkOrderImport.php +6 -1
- package/server/src/Integrations/Lalamove/Lalamove.php +3 -7
- package/server/src/Integrations/Lalamove/LalamoveServiceType.php +5 -8
- package/server/src/Jobs/CheckGeofenceDwell.php +41 -16
- package/server/src/Jobs/FinalizeApiOrderCreation.php +17 -2
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +11 -1
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +29 -16
- package/server/src/Jobs/ProcessAllocationJob.php +59 -25
- package/server/src/Jobs/ReplayPositions.php +17 -10
- package/server/src/Jobs/SendPositionReplay.php +18 -7
- package/server/src/Jobs/SimulateDrivingRoute.php +14 -3
- package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +12 -2
- package/server/src/Listeners/HandleDeliveryCompletion.php +11 -1
- package/server/src/Listeners/HandleGeofenceDwelled.php +6 -1
- package/server/src/Listeners/HandleGeofenceEntered.php +4 -2
- package/server/src/Listeners/HandleGeofenceExited.php +6 -1
- package/server/src/Listeners/HandleOrderCanceled.php +18 -3
- package/server/src/Listeners/HandleOrderDispatchFailed.php +12 -2
- package/server/src/Listeners/HandleOrderDispatched.php +61 -26
- package/server/src/Listeners/HandleOrderDriverAssigned.php +12 -2
- package/server/src/Listeners/HandleUserRemovedFromCompany.php +19 -6
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +24 -4
- package/server/src/Listeners/NotifyOrderEvent.php +11 -6
- package/server/src/Models/Contact.php +6 -7
- package/server/src/Models/Device.php +26 -3
- package/server/src/Models/Driver.php +0 -6
- package/server/src/Models/Entity.php +4 -4
- package/server/src/Models/Equipment.php +1 -1
- package/server/src/Models/FuelReport.php +26 -1
- package/server/src/Models/IntegratedVendor.php +2 -2
- package/server/src/Models/Issue.php +5 -1
- package/server/src/Models/MaintenanceSchedule.php +25 -10
- package/server/src/Models/Order.php +18 -15
- package/server/src/Models/OrderConfig.php +1 -1
- package/server/src/Models/Payload.php +9 -16
- package/server/src/Models/Place.php +20 -13
- package/server/src/Models/Sensor.php +24 -0
- package/server/src/Models/ServiceArea.php +3 -6
- package/server/src/Models/ServiceQuote.php +5 -0
- package/server/src/Models/ServiceRate.php +13 -6
- package/server/src/Models/Telematic.php +2 -2
- package/server/src/Models/TrackingNumber.php +55 -10
- package/server/src/Models/TrackingStatus.php +24 -4
- package/server/src/Models/Waypoint.php +44 -10
- package/server/src/Models/Zone.php +3 -6
- package/server/src/Notifications/DriverArrivedAtGeofence.php +1 -1
- package/server/src/Notifications/OrderDispatched.php +3 -9
- package/server/src/Notifications/OrderPing.php +3 -9
- package/server/src/Observers/CategoryObserver.php +6 -1
- package/server/src/Observers/CompanyObserver.php +5 -0
- package/server/src/Observers/CompanyUserObserver.php +12 -2
- package/server/src/Observers/DriverObserver.php +19 -4
- package/server/src/Observers/FleetObserver.php +6 -1
- package/server/src/Observers/PurchaseRateObserver.php +66 -11
- package/server/src/Observers/ServiceAreaObserver.php +12 -2
- package/server/src/Observers/ServiceRateObserver.php +21 -6
- package/server/src/Observers/TrackingNumberObserver.php +19 -4
- package/server/src/Observers/UserObserver.php +6 -1
- package/server/src/Observers/VehicleObserver.php +27 -7
- package/server/src/Observers/WorkOrderObserver.php +27 -7
- package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +8 -3
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +2 -2
- package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
- package/server/src/Providers/NotificationServiceProvider.php +6 -0
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +36 -26
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +18 -5
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +6 -1
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +26 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +9 -3
- package/server/src/Support/Analytics/FuelProviderSummary.php +14 -5
- package/server/src/Support/Analytics/GeofenceViolations.php +40 -20
- package/server/src/Support/Analytics/IssuesInsights.php +38 -18
- package/server/src/Support/Analytics/MaintenanceOverview.php +51 -21
- package/server/src/Support/Analytics/OnTimeDelivery.php +3 -2
- package/server/src/Support/Analytics/OperationsPulse.php +59 -35
- package/server/src/Support/Analytics/OrdersByStatus.php +12 -7
- package/server/src/Support/Analytics/RevenueTrend.php +16 -6
- package/server/src/Support/Analytics/TopDrivers.php +3 -2
- package/server/src/Support/FleetOps.php +109 -104
- package/server/src/Support/FuelProviders/FuelProviderService.php +10 -1
- package/server/src/Support/Geocoding.php +52 -14
- package/server/src/Support/GeofenceIntersectionService.php +47 -27
- package/server/src/Support/LiveOrderQuery.php +6 -1
- package/server/src/Support/Metrics/ActiveRevenueQuery.php +3 -2
- package/server/src/Support/Metrics/FuelCostsMetric.php +6 -1
- package/server/src/Support/ResolvesOrderServiceStops.php +10 -0
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +9 -4
- package/server/src/Support/Utils.php +122 -16
- package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +6 -1
- package/server/src/Tracking/Providers/OsrmTrackingProvider.php +11 -6
- package/server/src/Tracking/TrackingProviderManager.php +2 -3
- package/server/src/Tracking/TrackingProviderRegistry.php +8 -4
- package/server/src/Traits/PayloadAccessors.php +6 -1
- package/server/src/routes.php +24 -17
- package/server/tests/AiOperationalQueryCapabilityTest.php +457 -1
- package/server/tests/AiPreviewCapabilityContractsTest.php +180 -0
- package/server/tests/AnalyticsRoutesTest.php +380 -1
- package/server/tests/AnalyticsWidgetContractsTest.php +740 -0
- package/server/tests/ApiContactControllerContractsTest.php +284 -0
- package/server/tests/ApiCustomerControllerContractsTest.php +1130 -0
- package/server/tests/ApiDriverControllerContractsTest.php +603 -0
- package/server/tests/ApiEntityControllerContractsTest.php +329 -0
- package/server/tests/ApiEquipmentControllerContractsTest.php +239 -0
- package/server/tests/ApiFleetControllerContractsTest.php +176 -0
- package/server/tests/ApiFuelReportControllerContractsTest.php +217 -0
- package/server/tests/ApiIssueControllerContractsTest.php +217 -0
- package/server/tests/ApiOrderControllerContractsTest.php +1030 -0
- package/server/tests/ApiPartControllerContractsTest.php +251 -0
- package/server/tests/ApiPayloadControllerContractsTest.php +314 -0
- package/server/tests/ApiPlaceControllerContractsTest.php +541 -0
- package/server/tests/ApiPurchaseRateControllerContractsTest.php +283 -0
- package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +442 -0
- package/server/tests/ApiServiceQuoteControllerContractsTest.php +322 -0
- package/server/tests/ApiTrackingNumberControllerContractsTest.php +225 -0
- package/server/tests/ApiTrackingStatusControllerContractsTest.php +254 -0
- package/server/tests/ApiVehicleControllerContractsTest.php +368 -0
- package/server/tests/ApiVendorControllerContractsTest.php +199 -0
- package/server/tests/AssetAndPlaceSearchContractsTest.php +220 -0
- package/server/tests/BackendSmallModelContractsTest.php +354 -0
- package/server/tests/CapacityAllocationEngineTest.php +56 -0
- package/server/tests/CommandContractsTest.php +2695 -0
- package/server/tests/CompactResourceSerializationTest.php +2838 -0
- package/server/tests/ControllerFilterContractsTest.php +1273 -0
- package/server/tests/ControllerHelperContractsTest.php +1740 -0
- package/server/tests/ControllerSerializationContractsTest.php +141 -0
- package/server/tests/CoverageSummaryScriptTest.php +93 -0
- package/server/tests/CustomerContactIdentitySafetyTest.php +1 -6
- package/server/tests/CustomerEndpointTest.php +169 -2
- package/server/tests/DriverFilterExecutionTest.php +263 -0
- package/server/tests/DriverGeofenceProcessingTest.php +153 -0
- package/server/tests/DriverResourceSerializationTest.php +245 -0
- package/server/tests/DriverSchedulingTraitContractsTest.php +259 -0
- package/server/tests/EventContractsTest.php +1259 -0
- package/server/tests/ExceptionContractsTest.php +81 -0
- package/server/tests/ExpansionContractsTest.php +242 -0
- package/server/tests/ExportCoverageTest.php +116 -0
- package/server/tests/Feature/Http/Api/ContactControllerPhotoAndDeleteTest.php +199 -0
- package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +831 -0
- package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +347 -0
- package/server/tests/Feature/Http/Api/DeviceControllerCrudTest.php +176 -0
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +530 -0
- package/server/tests/Feature/Http/Api/DriverControllerHelpersTest.php +240 -0
- package/server/tests/Feature/Http/Api/DriverControllerLookupTest.php +186 -0
- package/server/tests/Feature/Http/Api/DriverControllerOrganizationsTest.php +112 -0
- package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +207 -0
- package/server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php +329 -0
- package/server/tests/Feature/Http/Api/DriverControllerTrackTest.php +383 -0
- package/server/tests/Feature/Http/Api/EntityControllerHelpersTest.php +150 -0
- package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +352 -0
- package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +461 -0
- package/server/tests/Feature/Http/Api/LabelControllerSubjectsTest.php +148 -0
- package/server/tests/Feature/Http/Api/OrderConfigControllerTest.php +112 -0
- package/server/tests/Feature/Http/Api/OrderControllerCreateTest.php +279 -0
- package/server/tests/Feature/Http/Api/OrderControllerIntegratedVendorTest.php +295 -0
- package/server/tests/Feature/Http/Api/OrderControllerLifecycleActionsTest.php +279 -0
- package/server/tests/Feature/Http/Api/OrderControllerLookupHelpersTest.php +207 -0
- package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +282 -0
- package/server/tests/Feature/Http/Api/OrderControllerPersistenceHelpersTest.php +216 -0
- package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +184 -0
- package/server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php +663 -0
- package/server/tests/Feature/Http/Api/PayloadControllerHelpersTest.php +134 -0
- package/server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php +479 -0
- package/server/tests/Feature/Http/Api/PurchaseRateControllerHelpersTest.php +176 -0
- package/server/tests/Feature/Http/Api/PurchaseRateControllerOrderCreationTest.php +268 -0
- package/server/tests/Feature/Http/Api/SensorControllerCrudTest.php +239 -0
- package/server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php +187 -0
- package/server/tests/Feature/Http/Api/ServiceQuoteControllerQueryTest.php +405 -0
- package/server/tests/Feature/Http/Api/ServiceQuoteControllerVendorBridgeTest.php +212 -0
- package/server/tests/Feature/Http/Api/ServiceRateControllerContractsTest.php +263 -0
- package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +272 -0
- package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +160 -0
- package/server/tests/Feature/Http/Api/TrackingStatusControllerHelpersTest.php +165 -0
- package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +483 -0
- package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +326 -0
- package/server/tests/Feature/Http/Api/WorkOrderControllerHelpersTest.php +130 -0
- package/server/tests/Feature/Http/Api/ZoneControllerBordersAndSeamsTest.php +294 -0
- package/server/tests/Feature/Http/FleetOpsRouteRegistrationTest.php +189 -0
- package/server/tests/Feature/Http/Internal/AnalyticsControllerSeamsTest.php +53 -0
- package/server/tests/Feature/Http/Internal/ContactControllerContractsTest.php +240 -0
- package/server/tests/Feature/Http/Internal/ContactControllerCustomerGuardsTest.php +128 -0
- package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +294 -0
- package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +196 -0
- package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +370 -0
- package/server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php +151 -0
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +1167 -0
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +233 -0
- package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +710 -0
- package/server/tests/Feature/Http/Internal/DriverControllerHelpersTest.php +218 -0
- package/server/tests/Feature/Http/Internal/FleetAssignmentsAndTriggersTest.php +198 -0
- package/server/tests/Feature/Http/Internal/FleetOpsLookupControllerDbTest.php +90 -0
- package/server/tests/Feature/Http/Internal/HubControllerTest.php +206 -0
- package/server/tests/Feature/Http/Internal/IssueControllerContractsTest.php +404 -0
- package/server/tests/Feature/Http/Internal/IssueControllerExportImportTest.php +118 -0
- package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +246 -0
- package/server/tests/Feature/Http/Internal/LiveControllerTest.php +778 -0
- package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerContractsTest.php +263 -0
- package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerSurfacesTest.php +214 -0
- package/server/tests/Feature/Http/Internal/MetricsControllerContractsTest.php +249 -0
- package/server/tests/Feature/Http/Internal/MetricsControllerHelpersTest.php +66 -0
- package/server/tests/Feature/Http/Internal/MorphControllerHelpersTest.php +125 -0
- package/server/tests/Feature/Http/Internal/NavigatorControllerHelpersTest.php +201 -0
- package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +928 -0
- package/server/tests/Feature/Http/Internal/OrchestrationControllerHelpersTest.php +214 -0
- package/server/tests/Feature/Http/Internal/OrchestrationControllerImportOrdersTest.php +340 -0
- package/server/tests/Feature/Http/Internal/OrderConfigControllerContractsTest.php +314 -0
- package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +663 -0
- package/server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php +507 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +1742 -0
- package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +488 -0
- package/server/tests/Feature/Http/Internal/OrderControllerHelperSeamsTest.php +298 -0
- package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +267 -0
- package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +179 -0
- package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +80 -0
- package/server/tests/Feature/Http/Internal/PaymentControllerPaymentsTest.php +154 -0
- package/server/tests/Feature/Http/Internal/PlaceControllerTest.php +252 -0
- package/server/tests/Feature/Http/Internal/PositionControllerReplayMetricsTest.php +163 -0
- package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +219 -0
- package/server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php +151 -0
- package/server/tests/Feature/Http/Internal/ServiceQuoteControllerHelpersTest.php +208 -0
- package/server/tests/Feature/Http/Internal/ServiceQuoteControllerQueryRecordTest.php +217 -0
- package/server/tests/Feature/Http/Internal/ServiceQuoteControllerVendorBridgeTest.php +239 -0
- package/server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php +271 -0
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +133 -0
- package/server/tests/Feature/Http/Internal/SmallInternalControllersTest.php +180 -0
- package/server/tests/Feature/Http/Internal/TelematicControllerEndpointsTest.php +417 -0
- package/server/tests/Feature/Http/Internal/VehicleControllerEndpointsTest.php +361 -0
- package/server/tests/Feature/Http/Internal/VendorControllerEndpointsTest.php +203 -0
- package/server/tests/Feature/Http/Internal/WorkOrderAndConfigSurfacesTest.php +289 -0
- package/server/tests/Feature/Http/Internal/WorkOrderControllerContractsTest.php +199 -0
- package/server/tests/Feature/Http/TelematicWebhookIngestTest.php +343 -0
- package/server/tests/Feature/Http/Telematics/TelematicWebhookControllerProcessingTest.php +254 -0
- package/server/tests/FleetOpsSupportContractsTest.php +187 -0
- package/server/tests/FlespiProviderContractsTest.php +244 -0
- package/server/tests/FlowResourceTest.php +295 -0
- package/server/tests/FuelProviderConnectionControllerContractsTest.php +303 -0
- package/server/tests/FuelProviderImplementationTest.php +753 -0
- package/server/tests/FuelProviderResourceContractsTest.php +171 -0
- package/server/tests/FuelProviderTransactionControllerContractsTest.php +198 -0
- package/server/tests/GeofenceIntersectionServiceContractsTest.php +164 -0
- package/server/tests/HOSConstraintContractsTest.php +128 -0
- package/server/tests/HubAndGettingStartedContractsTest.php +158 -0
- package/server/tests/ImportContractsTest.php +191 -0
- package/server/tests/InternalCustomerControllerContractsTest.php +238 -0
- package/server/tests/InternalFleetControllerContractsTest.php +389 -0
- package/server/tests/InternalImportExportControllerContractsTest.php +289 -0
- package/server/tests/InternalMetricsControllerContractsTest.php +69 -0
- package/server/tests/InternalPlaceWorkOrderControllerContractsTest.php +207 -0
- package/server/tests/InternalServiceQuoteCheckoutControllerContractsTest.php +470 -0
- package/server/tests/InternalVendorControllerContractsTest.php +331 -0
- package/server/tests/InventoryTelemetryModelContractsTest.php +516 -0
- package/server/tests/IssueTimelineContractsTest.php +191 -0
- package/server/tests/LabelPdfRenderingTest.php +57 -0
- package/server/tests/LalamoveIntegrationContractsTest.php +167 -0
- package/server/tests/LiveOrderQueryTest.php +145 -18
- package/server/tests/LookupAndGeocoderControllerContractsTest.php +357 -0
- package/server/tests/MaintainableTraitContractsTest.php +245 -0
- package/server/tests/MaintenanceScheduleControllerContractsTest.php +101 -0
- package/server/tests/MaintenanceWarrantyContractsTest.php +237 -0
- package/server/tests/ManifestControllerContractsTest.php +310 -0
- package/server/tests/MetricsRegistryTest.php +596 -10
- package/server/tests/MiddlewareContractsTest.php +150 -0
- package/server/tests/ModelAccessorContractsTest.php +3201 -0
- package/server/tests/MorphControllerContractsTest.php +410 -0
- package/server/tests/NavigatorControllerContractsTest.php +206 -0
- package/server/tests/NotificationAndMailContractsTest.php +677 -0
- package/server/tests/ObserverContractsTest.php +1414 -0
- package/server/tests/OperationalAlgorithmContractsTest.php +852 -0
- package/server/tests/OperationsMonitorEndpointTest.php +2 -1
- package/server/tests/OrchestrationPayloadBuilderTest.php +87 -0
- package/server/tests/OrderActivityFlowRegressionTest.php +354 -7
- package/server/tests/OrderDispatchListenerContractsTest.php +577 -0
- package/server/tests/OrderFilterExecutionTest.php +244 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +2 -1
- package/server/tests/PaymentControllerContractsTest.php +199 -0
- package/server/tests/PointResolutionTest.php +54 -0
- package/server/tests/ProcessAllocationJobContractsTest.php +224 -0
- package/server/tests/ProofAndSensorControllerContractsTest.php +44 -0
- package/server/tests/ProviderContractsTest.php +409 -0
- package/server/tests/PublicApiControllerInputContractsTest.php +294 -0
- package/server/tests/QueueJobContractsTest.php +764 -0
- package/server/tests/ReportSchemaContractsTest.php +271 -0
- package/server/tests/RequestContractsTest.php +1058 -0
- package/server/tests/RouteRegistrationExecutionTest.php +178 -0
- package/server/tests/RouteSequencingEngineTest.php +179 -0
- package/server/tests/RulesAndCastsTest.php +246 -0
- package/server/tests/SamsaraProviderContractsTest.php +272 -0
- package/server/tests/SearchControllerContractsTest.php +311 -0
- package/server/tests/ServiceRateRouteValidationTest.php +76 -0
- package/server/tests/SettingControllerContractsTest.php +410 -0
- package/server/tests/SmallControllerContractsTest.php +480 -0
- package/server/tests/SupportAnalyticsCustomerAuthContractsTest.php +221 -0
- package/server/tests/SupportJobAndAiCoverageTest.php +812 -0
- package/server/tests/SupportServiceContractsTest.php +287 -0
- package/server/tests/SupportValueObjectsTest.php +155 -0
- package/server/tests/TelematicControllerContractsTest.php +185 -0
- package/server/tests/TelematicProviderRegistryTest.php +167 -0
- package/server/tests/TelematicServiceHelperContractsTest.php +315 -0
- package/server/tests/TelematicWebhookControllerContractsTest.php +253 -0
- package/server/tests/TelematicsHardeningTest.php +642 -86
- package/server/tests/TrackingIntelligenceTest.php +644 -1
- package/server/tests/TrackingNumberTraitContractsTest.php +106 -0
- package/server/tests/Unit/Casts/SpatialCastBranchesTest.php +75 -0
- package/server/tests/Unit/Console/AssignCustomerRolesCommandTest.php +102 -0
- package/server/tests/Unit/Console/AssignDriverRolesCommandTest.php +113 -0
- package/server/tests/Unit/Console/Commands/FixLegacyOrderConfigsHelpersTest.php +108 -0
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +265 -0
- package/server/tests/Unit/Console/Commands/ReplayVehicleLocationsCommandTest.php +320 -0
- package/server/tests/Unit/Console/Commands/SimulateGeofenceEventsCommandTest.php +207 -0
- package/server/tests/Unit/Console/FixCommandsAndMaintenanceControllerTest.php +256 -0
- package/server/tests/Unit/Console/FleetOpsCommandsTest.php +198 -0
- package/server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php +252 -0
- package/server/tests/Unit/Console/SendMaintenanceRemindersTest.php +212 -0
- package/server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php +232 -0
- package/server/tests/Unit/Console/SyncTelematicsCommandTest.php +136 -0
- package/server/tests/Unit/Constraints/HOSConstraintTest.php +158 -0
- package/server/tests/Unit/Events/EventModelRecordTest.php +93 -0
- package/server/tests/Unit/Events/OrderDispatchFailedTest.php +34 -0
- package/server/tests/Unit/Exports/ExportCollectionsTest.php +142 -0
- package/server/tests/Unit/Exports/OperationalExportMappingTest.php +651 -0
- package/server/tests/Unit/Exports/VehicleExportTest.php +284 -0
- package/server/tests/Unit/Flow/EventFiringTest.php +95 -0
- package/server/tests/Unit/Http/Api/QueryWithRequestSeamsTest.php +198 -0
- package/server/tests/Unit/Http/Api/SensorInputTest.php +95 -0
- package/server/tests/Unit/Http/Filter/DeviceFilterTest.php +199 -0
- package/server/tests/Unit/Http/Filter/EmptyValueGuardsTest.php +66 -0
- package/server/tests/Unit/Http/Filter/FilterDateRangesTest.php +72 -0
- package/server/tests/Unit/Http/Filter/FuelProviderTransactionFilterTest.php +172 -0
- package/server/tests/Unit/Http/Filter/InternalRelationUuidLookupTest.php +135 -0
- package/server/tests/Unit/Http/Filter/IssueFilterTest.php +129 -0
- package/server/tests/Unit/Http/Filter/MaintenanceScheduleFilterTest.php +69 -0
- package/server/tests/Unit/Http/Filter/PartFilterTest.php +173 -0
- package/server/tests/Unit/Http/Internal/ControllerDelegationSeamsTest.php +136 -0
- package/server/tests/Unit/Http/Internal/ControllerExcelSeamsTest.php +89 -0
- package/server/tests/Unit/Http/Middleware/SetupDriverSessionTest.php +32 -0
- package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +68 -0
- package/server/tests/Unit/Http/Requests/FleetActionRequestTest.php +80 -0
- package/server/tests/Unit/Http/Requests/RequestAuthorizationTest.php +50 -0
- package/server/tests/Unit/Http/Resources/CustomerResourceTest.php +108 -0
- package/server/tests/Unit/Http/Resources/EntityCustomerTypeTest.php +35 -0
- package/server/tests/Unit/Http/Resources/FleetResourceTest.php +102 -0
- package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +210 -0
- package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +194 -0
- package/server/tests/Unit/Http/Resources/MaintenanceResourcesTest.php +182 -0
- package/server/tests/Unit/Http/Resources/OrderAndDriverResourceTest.php +121 -0
- package/server/tests/Unit/Http/Resources/OrderConfigResourceTest.php +47 -0
- package/server/tests/Unit/Http/Resources/PayloadResourceTest.php +111 -0
- package/server/tests/Unit/Http/Resources/RelationResourceFallbacksTest.php +32 -0
- package/server/tests/Unit/Http/Resources/ResourceRelationResolutionTest.php +55 -0
- package/server/tests/Unit/Http/Resources/TrackingNumberResourceTest.php +174 -0
- package/server/tests/Unit/Http/Resources/WaypointResourceTest.php +295 -0
- package/server/tests/Unit/Http/Resources/WorkOrderResourceTest.php +164 -0
- package/server/tests/Unit/Imports/ImportDelegationTest.php +151 -0
- package/server/tests/Unit/Integrations/Lalamove/LalamoveDeliveryStopTest.php +79 -0
- package/server/tests/Unit/Integrations/Lalamove/LalamoveLifecycleTest.php +211 -0
- package/server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php +381 -0
- package/server/tests/Unit/Integrations/Lalamove/LalamoveServiceTypeTest.php +51 -0
- package/server/tests/Unit/Integrations/LalamoveQuotationTest.php +194 -0
- package/server/tests/Unit/Integrations/LalamoveResponseHandlingTest.php +58 -0
- package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +190 -0
- package/server/tests/Unit/Jobs/ReplayPositionsTest.php +57 -0
- package/server/tests/Unit/Jobs/SimulateWaypointReachedTest.php +92 -0
- package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +256 -0
- package/server/tests/Unit/Listeners/HandleOrderDispatchFailedListenerTest.php +101 -0
- package/server/tests/Unit/Listeners/HandleOrderDispatchedConcreteTest.php +203 -0
- package/server/tests/Unit/Listeners/HandleOrderDispatchedHelpersTest.php +186 -0
- package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +65 -0
- package/server/tests/Unit/Listeners/ListenerDelegationSeamsTest.php +83 -0
- package/server/tests/Unit/Models/AssetTest.php +469 -0
- package/server/tests/Unit/Models/ContactCustomerUserTest.php +370 -0
- package/server/tests/Unit/Models/ContactTest.php +577 -0
- package/server/tests/Unit/Models/DeviceEventTest.php +481 -0
- package/server/tests/Unit/Models/DeviceLastPositionDefaultTest.php +128 -0
- package/server/tests/Unit/Models/DriverModelHelpersTest.php +268 -0
- package/server/tests/Unit/Models/DriverTest.php +401 -0
- package/server/tests/Unit/Models/EntityTest.php +171 -0
- package/server/tests/Unit/Models/EquipmentTest.php +434 -0
- package/server/tests/Unit/Models/FleetTest.php +153 -0
- package/server/tests/Unit/Models/FuelProviderTransactionTest.php +82 -0
- package/server/tests/Unit/Models/FuelReportImportAndLabelsTest.php +193 -0
- package/server/tests/Unit/Models/ImportRowFallbackSeamsTest.php +100 -0
- package/server/tests/Unit/Models/IntegratedVendorLifecycleTest.php +203 -0
- package/server/tests/Unit/Models/IntegratedVendorTest.php +135 -0
- package/server/tests/Unit/Models/IssueTest.php +160 -0
- package/server/tests/Unit/Models/MaintenanceScheduleTest.php +319 -0
- package/server/tests/Unit/Models/MaintenanceTest.php +277 -0
- package/server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php +161 -0
- package/server/tests/Unit/Models/ManifestStopTest.php +121 -0
- package/server/tests/Unit/Models/ManifestTest.php +163 -0
- package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +149 -0
- package/server/tests/Unit/Models/OrderConfigTest.php +341 -0
- package/server/tests/Unit/Models/OrderDynamicContractsTest.php +190 -0
- package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +426 -0
- package/server/tests/Unit/Models/OrderPayloadRouteTest.php +227 -0
- package/server/tests/Unit/Models/OrderPurchaseAndDriversTest.php +188 -0
- package/server/tests/Unit/Models/OrderTest.php +917 -0
- package/server/tests/Unit/Models/PartTest.php +399 -0
- package/server/tests/Unit/Models/PayloadTest.php +710 -0
- package/server/tests/Unit/Models/PayloadWaypointsAndEntitiesTest.php +317 -0
- package/server/tests/Unit/Models/PlaceCreationAndImportTest.php +384 -0
- package/server/tests/Unit/Models/PlaceGeocodingResultsTest.php +253 -0
- package/server/tests/Unit/Models/PlaceSharedAndGeocodingTest.php +271 -0
- package/server/tests/Unit/Models/PlaceTest.php +589 -0
- package/server/tests/Unit/Models/PositionTest.php +49 -0
- package/server/tests/Unit/Models/RouteTest.php +55 -0
- package/server/tests/Unit/Models/SensorLastPositionDefaultTest.php +130 -0
- package/server/tests/Unit/Models/SensorReadingsTest.php +168 -0
- package/server/tests/Unit/Models/SensorTest.php +284 -0
- package/server/tests/Unit/Models/ServiceAreaGeometryTest.php +129 -0
- package/server/tests/Unit/Models/ServiceAreaTest.php +125 -0
- package/server/tests/Unit/Models/ServiceQuoteTest.php +617 -0
- package/server/tests/Unit/Models/ServiceRateFeeTest.php +73 -0
- package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +428 -0
- package/server/tests/Unit/Models/ServiceRateTest.php +533 -0
- package/server/tests/Unit/Models/SmallModelSurfacesTest.php +294 -0
- package/server/tests/Unit/Models/TrackingNumberTest.php +199 -0
- package/server/tests/Unit/Models/TrackingStatusTest.php +131 -0
- package/server/tests/Unit/Models/VehicleDeviceTest.php +115 -0
- package/server/tests/Unit/Models/VehiclePositionsAndImportTest.php +220 -0
- package/server/tests/Unit/Models/VehicleTest.php +266 -0
- package/server/tests/Unit/Models/VendorRelationsAndImportTest.php +107 -0
- package/server/tests/Unit/Models/WarrantyTest.php +187 -0
- package/server/tests/Unit/Models/WaypointTest.php +366 -0
- package/server/tests/Unit/Models/WorkOrderTest.php +272 -0
- package/server/tests/Unit/Models/ZoneTest.php +83 -0
- package/server/tests/Unit/Notifications/MonitoringNotificationsTest.php +136 -0
- package/server/tests/Unit/Notifications/OrderAssignedChannelsTest.php +53 -0
- package/server/tests/Unit/Notifications/OrderDispatchedChannelsTest.php +81 -0
- package/server/tests/Unit/Notifications/OrderFailedChannelsTest.php +73 -0
- package/server/tests/Unit/Notifications/OrderPingChannelsTest.php +69 -0
- package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +185 -0
- package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +133 -0
- package/server/tests/Unit/Observers/ObserverHelperExecutionTest.php +135 -0
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +267 -0
- package/server/tests/Unit/Observers/PurchaseRateObserverAndOrderJobsTest.php +169 -0
- package/server/tests/Unit/Orchestration/Engines/DriverAssignmentEngineTest.php +393 -0
- package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +397 -0
- package/server/tests/Unit/Orchestration/OrchestrationEngineRegistryTest.php +45 -0
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +253 -0
- package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +59 -0
- package/server/tests/Unit/Rules/ResolvableVehicleTest.php +85 -0
- package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +120 -0
- package/server/tests/Unit/Support/Ai/Capabilities/CreateOrderPreviewCapabilityTest.php +385 -0
- package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +226 -0
- package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +237 -0
- package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +124 -0
- package/server/tests/Unit/Support/AlgoEdgeBranchesTest.php +24 -0
- package/server/tests/Unit/Support/Analytics/GeofenceViolationsTest.php +109 -0
- package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +205 -0
- package/server/tests/Unit/Support/Analytics/MaintenanceOverviewTest.php +236 -0
- package/server/tests/Unit/Support/Analytics/OnTimeDeliveryAndTopDriversTest.php +250 -0
- package/server/tests/Unit/Support/CreateOrderPreviewDraftingTest.php +235 -0
- package/server/tests/Unit/Support/CustomerAuthTest.php +132 -0
- package/server/tests/Unit/Support/FuelEfficiencyAndCanceledHandlersTest.php +164 -0
- package/server/tests/Unit/Support/FuelProviderRegistryResolutionTest.php +51 -0
- package/server/tests/Unit/Support/FuelProviderServiceMatchingTest.php +280 -0
- package/server/tests/Unit/Support/GeocodingInjectedClientTest.php +268 -0
- package/server/tests/Unit/Support/GeocodingLocateTest.php +32 -0
- package/server/tests/Unit/Support/GeofenceIntersectionServiceTest.php +188 -0
- package/server/tests/Unit/Support/GettingStartedTest.php +164 -0
- package/server/tests/Unit/Support/GuardReturnSeamsTest.php +172 -0
- package/server/tests/Unit/Support/IntegratedVendorsResolverTest.php +250 -0
- package/server/tests/Unit/Support/IssuesInsightsAndAllocationJobTest.php +156 -0
- package/server/tests/Unit/Support/MagicAccessorSeamsTest.php +54 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +215 -0
- package/server/tests/Unit/Support/Metrics/AvgOrderValueMetricTest.php +308 -0
- package/server/tests/Unit/Support/Metrics/CountMetricsTest.php +280 -0
- package/server/tests/Unit/Support/Metrics/FuelCostsMetricTest.php +102 -0
- package/server/tests/Unit/Support/Metrics/MetricQuerySeamsTest.php +129 -0
- package/server/tests/Unit/Support/Metrics/OrdersScheduledMetricTest.php +146 -0
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +410 -0
- package/server/tests/Unit/Support/OperationalQueryDistributionTest.php +152 -0
- package/server/tests/Unit/Support/OperationsPulseTest.php +130 -0
- package/server/tests/Unit/Support/OptimizeOrderRouteCapabilityTest.php +147 -0
- package/server/tests/Unit/Support/OrchestrationPayloadBuilderTest.php +135 -0
- package/server/tests/Unit/Support/OrchestrationRouteAndVehicleTasksTest.php +254 -0
- package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +247 -0
- package/server/tests/Unit/Support/PetroAppFuelProviderTest.php +165 -0
- package/server/tests/Unit/Support/PlaceSearchRankingTest.php +191 -0
- package/server/tests/Unit/Support/QuerySeamsTest.php +309 -0
- package/server/tests/Unit/Support/ResolvesOrderServiceStopsDbTest.php +303 -0
- package/server/tests/Unit/Support/ResolvesOrderServiceStopsTest.php +382 -0
- package/server/tests/Unit/Support/SkipAndFallbackGuardsTest.php +70 -0
- package/server/tests/Unit/Support/TelematicCredentialResolutionTest.php +31 -0
- package/server/tests/Unit/Support/TelematicServiceTest.php +256 -0
- package/server/tests/Unit/Support/Telematics/Providers/AbstractProviderTest.php +154 -0
- package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTest.php +191 -0
- package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php +138 -0
- package/server/tests/Unit/Support/Telematics/Providers/GeotabProviderTest.php +329 -0
- package/server/tests/Unit/Support/Telematics/Providers/ProviderTransportSeamsTest.php +133 -0
- package/server/tests/Unit/Support/Telematics/Providers/SafeeProviderTest.php +487 -0
- package/server/tests/Unit/Support/Telematics/TelematicProviderRegistryTest.php +39 -0
- package/server/tests/Unit/Support/Telematics/TelematicServiceLifecycleTest.php +711 -0
- package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +364 -0
- package/server/tests/Unit/Support/UtilsAdditionalTest.php +256 -0
- package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +382 -0
- package/server/tests/Unit/Tracking/TrackingContextTest.php +114 -0
- package/server/tests/Unit/Tracking/TrackingIntelligenceAndContextTest.php +477 -0
- package/server/tests/Unit/Tracking/TrackingProviderManagerFallbacksTest.php +208 -0
- package/server/tests/Unit/Traits/HasTrackingNumberTest.php +303 -0
- package/server/tests/Unit/Traits/PayloadAccessorsTest.php +150 -0
- package/server/tests/UtilsContractsTest.php +244 -0
- package/server/tests/VehicleControllerHelperContractsTest.php +488 -0
- package/server/tests/VehicleDataParsingTest.php +45 -0
- package/server/tests/VehicleResourceContractsTest.php +313 -0
- package/translations/bg-bg.yaml +2 -0
- package/translations/en-us.yaml +2 -0
- package/translations/es-pa.yaml +2 -0
- package/translations/fr-fr.yaml +2 -0
- package/translations/mn-mn.yaml +2 -0
- package/translations/pt-br.yaml +2 -0
- package/translations/ru-ru.yaml +2 -0
- package/server/tests/DeviceFilterTest.php +0 -44
- package/server/tests/LiveControllerViewportTest.php +0 -52
|
@@ -0,0 +1,2695 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Carbon\Carbon;
|
|
4
|
+
use Fleetbase\FleetOps\Console\Commands\AssignCustomerRoles;
|
|
5
|
+
use Fleetbase\FleetOps\Console\Commands\AssignDriverRoles;
|
|
6
|
+
use Fleetbase\FleetOps\Console\Commands\AuditCustomerUserConflicts;
|
|
7
|
+
use Fleetbase\FleetOps\Console\Commands\DebugOrderTracker;
|
|
8
|
+
use Fleetbase\FleetOps\Console\Commands\DispatchAdhocOrders;
|
|
9
|
+
use Fleetbase\FleetOps\Console\Commands\DispatchOrders;
|
|
10
|
+
use Fleetbase\FleetOps\Console\Commands\FixCustomerCompanies;
|
|
11
|
+
use Fleetbase\FleetOps\Console\Commands\FixDriverCompanies;
|
|
12
|
+
use Fleetbase\FleetOps\Console\Commands\FixLegacyOrderConfigs;
|
|
13
|
+
use Fleetbase\FleetOps\Console\Commands\ProcessMaintenanceTriggers;
|
|
14
|
+
use Fleetbase\FleetOps\Console\Commands\PurgeUnpurchasedServiceQuotes;
|
|
15
|
+
use Fleetbase\FleetOps\Console\Commands\SendDriverNotification;
|
|
16
|
+
use Fleetbase\FleetOps\Console\Commands\SendMaintenanceReminders;
|
|
17
|
+
use Fleetbase\FleetOps\Console\Commands\SimulateOrderRouteNavigation;
|
|
18
|
+
use Fleetbase\FleetOps\Console\Commands\SyncTelematics;
|
|
19
|
+
use Fleetbase\FleetOps\Console\Commands\TestEmail;
|
|
20
|
+
use Fleetbase\FleetOps\Console\Commands\TrackOrderDistanceAndTime;
|
|
21
|
+
use Fleetbase\FleetOps\Contracts\TelematicProviderDescriptor;
|
|
22
|
+
use Fleetbase\FleetOps\Models\Contact;
|
|
23
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
24
|
+
use Fleetbase\FleetOps\Models\MaintenanceSchedule;
|
|
25
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
26
|
+
use Fleetbase\FleetOps\Models\OrderConfig;
|
|
27
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
28
|
+
use Fleetbase\FleetOps\Models\WorkOrder;
|
|
29
|
+
use Fleetbase\FleetOps\Notifications\OrderAssigned;
|
|
30
|
+
use Fleetbase\FleetOps\Notifications\OrderPing;
|
|
31
|
+
use Fleetbase\FleetOps\Support\Telematics\TelematicProviderRegistry;
|
|
32
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
33
|
+
use Fleetbase\Models\Company;
|
|
34
|
+
use Illuminate\Console\Command;
|
|
35
|
+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
|
36
|
+
use Illuminate\Database\Eloquent\Model;
|
|
37
|
+
use Illuminate\Support\Facades\Cache;
|
|
38
|
+
|
|
39
|
+
if (!function_exists('Fleetbase\Traits\config')) {
|
|
40
|
+
eval('namespace Fleetbase\Traits; function config($key = null, $default = null) { return false; }');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!function_exists('Fleetbase\Models\config')) {
|
|
44
|
+
eval('namespace Fleetbase\Models; function config($key = null, $default = null) { return $default; }');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!function_exists('Fleetbase\FleetOps\Console\Commands\config')) {
|
|
48
|
+
eval('namespace Fleetbase\FleetOps\Console\Commands; function config($key = null, $default = null) { return $default; }');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!function_exists('Fleetbase\FleetOps\Console\Commands\now')) {
|
|
52
|
+
eval('namespace Fleetbase\FleetOps\Console\Commands; function now($timezone = null) { return \Carbon\Carbon::now($timezone); }');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class FleetOpsCommandCacheFake
|
|
56
|
+
{
|
|
57
|
+
public function __construct(private FleetOpsCommandLockFake $lock)
|
|
58
|
+
{
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public function lock($key, $seconds)
|
|
62
|
+
{
|
|
63
|
+
return $this->lock;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public function forget($key): bool
|
|
67
|
+
{
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class FleetOpsCommandLockFake
|
|
73
|
+
{
|
|
74
|
+
public bool $released = false;
|
|
75
|
+
|
|
76
|
+
public function __construct(private bool $locked)
|
|
77
|
+
{
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public function get(): bool
|
|
81
|
+
{
|
|
82
|
+
return $this->locked;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public function release(): void
|
|
86
|
+
{
|
|
87
|
+
$this->released = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class FleetOpsTelematicProviderRegistryFake extends TelematicProviderRegistry
|
|
92
|
+
{
|
|
93
|
+
public function __construct(Illuminate\Support\Collection $providers)
|
|
94
|
+
{
|
|
95
|
+
$this->providers = $providers;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public function all(): Illuminate\Support\Collection
|
|
99
|
+
{
|
|
100
|
+
return $this->providers;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class FleetOpsProcessMaintenanceTriggersProbe extends ProcessMaintenanceTriggers
|
|
105
|
+
{
|
|
106
|
+
public function callHelper(string $method, mixed ...$arguments): mixed
|
|
107
|
+
{
|
|
108
|
+
$reflection = new ReflectionMethod(ProcessMaintenanceTriggers::class, $method);
|
|
109
|
+
$reflection->setAccessible(true);
|
|
110
|
+
|
|
111
|
+
return $reflection->invoke($this, ...$arguments);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class FleetOpsProcessMaintenanceTriggersHandleFake extends ProcessMaintenanceTriggers
|
|
116
|
+
{
|
|
117
|
+
public array $createdWorkOrders = [];
|
|
118
|
+
public array $dispatchedEvents = [];
|
|
119
|
+
public array $messages = [];
|
|
120
|
+
public array $operations = [];
|
|
121
|
+
public Illuminate\Support\Collection $testSchedules;
|
|
122
|
+
public bool $existingOpen = false;
|
|
123
|
+
public int $workOrderCount = 0;
|
|
124
|
+
|
|
125
|
+
public function __construct(private array $testOptions)
|
|
126
|
+
{
|
|
127
|
+
parent::__construct();
|
|
128
|
+
$this->testSchedules = collect();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public function option($key = null)
|
|
132
|
+
{
|
|
133
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public function info($string, $verbosity = null)
|
|
137
|
+
{
|
|
138
|
+
$this->messages[] = ['info', $string];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public function line($string, $style = null, $verbosity = null)
|
|
142
|
+
{
|
|
143
|
+
$this->messages[] = ['line', $string];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protected function schedules(string $conn)
|
|
147
|
+
{
|
|
148
|
+
$this->operations[] = ['schedules', $conn];
|
|
149
|
+
|
|
150
|
+
return $this->testSchedules;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected function openWorkOrderExists(string $conn, MaintenanceSchedule $schedule): bool
|
|
154
|
+
{
|
|
155
|
+
$this->operations[] = ['openWorkOrderExists', $conn, $schedule->uuid];
|
|
156
|
+
|
|
157
|
+
return $this->existingOpen;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected function workOrderCount(string $conn): int
|
|
161
|
+
{
|
|
162
|
+
$this->operations[] = ['workOrderCount', $conn];
|
|
163
|
+
|
|
164
|
+
return $this->workOrderCount;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected function createWorkOrder(string $conn, array $attributes): WorkOrder
|
|
168
|
+
{
|
|
169
|
+
$this->operations[] = ['createWorkOrder', $conn];
|
|
170
|
+
$this->createdWorkOrders[] = $attributes;
|
|
171
|
+
|
|
172
|
+
$workOrder = new WorkOrder();
|
|
173
|
+
$workOrder->setRawAttributes(array_merge(['public_id' => 'work_order_created'], $attributes), true);
|
|
174
|
+
|
|
175
|
+
return $workOrder;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected function dispatchTriggeredEvent(MaintenanceSchedule $schedule, WorkOrder $workOrder): void
|
|
179
|
+
{
|
|
180
|
+
$this->dispatchedEvents[] = [$schedule->uuid, $workOrder->public_id];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class FleetOpsSendMaintenanceRemindersCommandFake extends SendMaintenanceReminders
|
|
185
|
+
{
|
|
186
|
+
public array $messages = [];
|
|
187
|
+
public array $sent = [];
|
|
188
|
+
public array $records = [];
|
|
189
|
+
public Illuminate\Support\Collection $testSchedules;
|
|
190
|
+
|
|
191
|
+
public function __construct(private array $testOptions, private array $alreadySent = [])
|
|
192
|
+
{
|
|
193
|
+
parent::__construct();
|
|
194
|
+
$this->testSchedules = collect();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public function option($key = null)
|
|
198
|
+
{
|
|
199
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public function info($string, $verbosity = null)
|
|
203
|
+
{
|
|
204
|
+
$this->messages[] = ['info', $string];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public function line($string, $style = null, $verbosity = null)
|
|
208
|
+
{
|
|
209
|
+
$this->messages[] = ['line', $string];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected function schedules(string $conn): Illuminate\Support\Collection
|
|
213
|
+
{
|
|
214
|
+
$this->messages[] = ['schedules', $conn];
|
|
215
|
+
|
|
216
|
+
return $this->testSchedules;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected function reminderAlreadySent(string $conn, MaintenanceSchedule $schedule, int $offsetDays, string $dueDateSnapshot): bool
|
|
220
|
+
{
|
|
221
|
+
$key = implode('|', [$conn, $schedule->uuid, $offsetDays, $dueDateSnapshot]);
|
|
222
|
+
|
|
223
|
+
return in_array($key, $this->alreadySent, true);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
protected function sendReminder(string $email, MaintenanceSchedule $schedule, int $offsetDays): void
|
|
227
|
+
{
|
|
228
|
+
$this->sent[] = [$email, $schedule->uuid, $offsetDays];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
protected function recordReminder(string $conn, MaintenanceSchedule $schedule, int $offsetDays, string $dueDateSnapshot): void
|
|
232
|
+
{
|
|
233
|
+
$this->records[] = [$conn, $schedule->uuid, $offsetDays, $dueDateSnapshot];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
class FleetOpsSimulateOrderFake extends Order
|
|
238
|
+
{
|
|
239
|
+
public ?Driver $assignedDriver = null;
|
|
240
|
+
|
|
241
|
+
public function loadAssignedDriver(): Order
|
|
242
|
+
{
|
|
243
|
+
$this->setRelation('driverAssigned', $this->assignedDriver);
|
|
244
|
+
|
|
245
|
+
return $this;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class FleetOpsSimulateOrderRouteNavigationCommandFake extends SimulateOrderRouteNavigation
|
|
250
|
+
{
|
|
251
|
+
public array $messages = [];
|
|
252
|
+
public array $events = [];
|
|
253
|
+
public int $pauses = 0;
|
|
254
|
+
public ?Order $order = null;
|
|
255
|
+
public ?Driver $driver = null;
|
|
256
|
+
public array $route = [];
|
|
257
|
+
public array $waypoints = [];
|
|
258
|
+
|
|
259
|
+
public function __construct(private array $testArguments)
|
|
260
|
+
{
|
|
261
|
+
parent::__construct();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public function argument($key = null)
|
|
265
|
+
{
|
|
266
|
+
return $key === null ? $this->testArguments : ($this->testArguments[$key] ?? null);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public function info($string, $verbosity = null)
|
|
270
|
+
{
|
|
271
|
+
$this->messages[] = ['info', $string];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public function error($string, $verbosity = null)
|
|
275
|
+
{
|
|
276
|
+
$this->messages[] = ['error', $string];
|
|
277
|
+
|
|
278
|
+
return Command::FAILURE;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public function promptQuestions(): array
|
|
282
|
+
{
|
|
283
|
+
return $this->promptForMissingArgumentsUsing();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
protected function findOrder(string $orderId): ?Order
|
|
287
|
+
{
|
|
288
|
+
$this->messages[] = ['findOrder', $orderId];
|
|
289
|
+
|
|
290
|
+
return $this->order;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
protected function findDriver(string $driverId): ?Driver
|
|
294
|
+
{
|
|
295
|
+
$this->messages[] = ['findDriver', $driverId];
|
|
296
|
+
|
|
297
|
+
return $this->driver;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
protected function getRoute(mixed $start, mixed $end): array
|
|
301
|
+
{
|
|
302
|
+
$this->messages[] = ['getRoute', (string) $start, (string) $end];
|
|
303
|
+
|
|
304
|
+
return $this->route;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
protected function decodePolyline(string $routeGeometry): array
|
|
308
|
+
{
|
|
309
|
+
$this->messages[] = ['decodePolyline', $routeGeometry];
|
|
310
|
+
|
|
311
|
+
return $this->waypoints;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
protected function dispatchLocationChanged(Driver $driver, mixed $waypoint, array $additionalData): void
|
|
315
|
+
{
|
|
316
|
+
$this->events[] = [$driver->public_id, (string) $waypoint, $additionalData];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
protected function pauseBetweenWaypoints(): void
|
|
320
|
+
{
|
|
321
|
+
$this->pauses++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class FleetOpsDispatchAdhocOrdersCommandFake extends DispatchAdhocOrders
|
|
326
|
+
{
|
|
327
|
+
public array $messages = [];
|
|
328
|
+
public array $tables = [];
|
|
329
|
+
public EloquentCollection $orders;
|
|
330
|
+
public EloquentCollection $drivers;
|
|
331
|
+
|
|
332
|
+
public function __construct(private array $testOptions)
|
|
333
|
+
{
|
|
334
|
+
parent::__construct();
|
|
335
|
+
$this->orders = new EloquentCollection();
|
|
336
|
+
$this->drivers = new EloquentCollection();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public function option($key = null)
|
|
340
|
+
{
|
|
341
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public function getDispatchableOrders(int $days = 2, int $intervalMinutes = 4, int $expiryHours = 72): EloquentCollection
|
|
345
|
+
{
|
|
346
|
+
$this->messages[] = ['getDispatchableOrders', $days, $intervalMinutes, $expiryHours];
|
|
347
|
+
|
|
348
|
+
return $this->orders;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
public function getNearbyDriversForOrder(Order $order, Point $pickup, int $distance, bool $testing = false): EloquentCollection
|
|
352
|
+
{
|
|
353
|
+
$this->messages[] = ['getNearbyDriversForOrder', $order->public_id, $distance, $testing];
|
|
354
|
+
|
|
355
|
+
return $this->drivers;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public function info($string, $verbosity = null)
|
|
359
|
+
{
|
|
360
|
+
$this->messages[] = ['info', $string];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public function alert($string, $verbosity = null)
|
|
364
|
+
{
|
|
365
|
+
$this->messages[] = ['alert', $string];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public function table($headers, $rows, $tableStyle = 'default', array $columnStyles = [])
|
|
369
|
+
{
|
|
370
|
+
$this->tables[] = [$headers, $rows];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
public function error($string, $verbosity = null)
|
|
374
|
+
{
|
|
375
|
+
$this->messages[] = ['error', $string];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
public function line($string, $style = null, $verbosity = null)
|
|
379
|
+
{
|
|
380
|
+
$this->messages[] = ['line', $string];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public function warn($string, $verbosity = null)
|
|
384
|
+
{
|
|
385
|
+
$this->messages[] = ['warn', $string];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
class FleetOpsDispatchAdhocOrdersQueryProbe extends DispatchAdhocOrders
|
|
390
|
+
{
|
|
391
|
+
public FleetOpsDispatchAdhocOrdersBuilderFake $orderQuery;
|
|
392
|
+
public FleetOpsDispatchAdhocOrdersBuilderFake $driverQuery;
|
|
393
|
+
|
|
394
|
+
public function __construct(private array $testOptions)
|
|
395
|
+
{
|
|
396
|
+
parent::__construct();
|
|
397
|
+
$this->orderQuery = new FleetOpsDispatchAdhocOrdersBuilderFake();
|
|
398
|
+
$this->driverQuery = new FleetOpsDispatchAdhocOrdersBuilderFake();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
public function option($key = null)
|
|
402
|
+
{
|
|
403
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
protected function newOrderQuery(string $connection)
|
|
407
|
+
{
|
|
408
|
+
$this->orderQuery->calls[] = ['connection', $connection];
|
|
409
|
+
|
|
410
|
+
return $this->orderQuery;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
protected function newDriverQuery()
|
|
414
|
+
{
|
|
415
|
+
return $this->driverQuery;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class FleetOpsDispatchAdhocOrdersBuilderFake
|
|
420
|
+
{
|
|
421
|
+
public array $calls = [];
|
|
422
|
+
public EloquentCollection $results;
|
|
423
|
+
|
|
424
|
+
public function __construct()
|
|
425
|
+
{
|
|
426
|
+
$this->results = new EloquentCollection();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
public function withoutGlobalScopes(): self
|
|
430
|
+
{
|
|
431
|
+
$this->calls[] = ['withoutGlobalScopes'];
|
|
432
|
+
|
|
433
|
+
return $this;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
public function where(...$arguments): self
|
|
437
|
+
{
|
|
438
|
+
$this->calls[] = ['where', $arguments];
|
|
439
|
+
|
|
440
|
+
return $this;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public function whereBetween(string $column, array $range): self
|
|
444
|
+
{
|
|
445
|
+
$this->calls[] = ['whereBetween', $column, $range];
|
|
446
|
+
|
|
447
|
+
return $this;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public function whereNull(string $column): self
|
|
451
|
+
{
|
|
452
|
+
$this->calls[] = ['whereNull', $column];
|
|
453
|
+
|
|
454
|
+
return $this;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
public function whereHas(string $relation, ?Closure $callback = null): self
|
|
458
|
+
{
|
|
459
|
+
$this->calls[] = ['whereHas', $relation];
|
|
460
|
+
|
|
461
|
+
if ($callback) {
|
|
462
|
+
$callback($this);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return $this;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
public function with(array $relations): self
|
|
469
|
+
{
|
|
470
|
+
$this->calls[] = ['with', $relations];
|
|
471
|
+
|
|
472
|
+
return $this;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public function whereNotNull(string $column): self
|
|
476
|
+
{
|
|
477
|
+
$this->calls[] = ['whereNotNull', $column];
|
|
478
|
+
|
|
479
|
+
return $this;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
public function whereRaw(string $sql): self
|
|
483
|
+
{
|
|
484
|
+
$this->calls[] = ['whereRaw', trim($sql)];
|
|
485
|
+
|
|
486
|
+
return $this;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
public function distanceSphere(string $column, Point $point, int $distance): self
|
|
490
|
+
{
|
|
491
|
+
$this->calls[] = ['distanceSphere', $column, $point, $distance];
|
|
492
|
+
|
|
493
|
+
return $this;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public function distanceSphereValue(string $column, Point $point): self
|
|
497
|
+
{
|
|
498
|
+
$this->calls[] = ['distanceSphereValue', $column, $point];
|
|
499
|
+
|
|
500
|
+
return $this;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public function orWhereHas(string $relation, ?Closure $callback = null): self
|
|
504
|
+
{
|
|
505
|
+
$this->calls[] = ['orWhereHas', $relation];
|
|
506
|
+
|
|
507
|
+
if ($callback) {
|
|
508
|
+
$callback($this);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return $this;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
public function get(): EloquentCollection
|
|
515
|
+
{
|
|
516
|
+
return $this->results;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
class FleetOpsDispatchAdhocOrdersOrderFake extends Order
|
|
521
|
+
{
|
|
522
|
+
public ?string $public_id = null;
|
|
523
|
+
public mixed $dispatched_at = null;
|
|
524
|
+
public ?string $company_uuid = null;
|
|
525
|
+
public bool $dispatchedForPing = false;
|
|
526
|
+
public mixed $pickupLocation;
|
|
527
|
+
public int $adhocDistance = 6000;
|
|
528
|
+
|
|
529
|
+
public function getPickupLocation(): mixed
|
|
530
|
+
{
|
|
531
|
+
return $this->pickupLocation;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
public function getAdhocPingDistance(): int
|
|
535
|
+
{
|
|
536
|
+
return $this->adhocDistance;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public function dispatch(bool $save = true): self
|
|
540
|
+
{
|
|
541
|
+
$this->dispatchedForPing = $save;
|
|
542
|
+
|
|
543
|
+
return $this;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
class FleetOpsDispatchAdhocOrdersDriverFake extends Driver
|
|
548
|
+
{
|
|
549
|
+
public ?string $public_id = null;
|
|
550
|
+
public ?string $name = null;
|
|
551
|
+
public array $notifications = [];
|
|
552
|
+
|
|
553
|
+
public function notify($instance): void
|
|
554
|
+
{
|
|
555
|
+
$this->notifications[] = $instance::class;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
class FleetOpsTrackOrderDistanceAndTimeProbe extends TrackOrderDistanceAndTime
|
|
560
|
+
{
|
|
561
|
+
public array $messages = [];
|
|
562
|
+
public FleetOpsTrackOrderQueryFake $query;
|
|
563
|
+
public FleetOpsTrackProgressBarFake $progressBar;
|
|
564
|
+
|
|
565
|
+
public function __construct(private array $testOptions, array $orders = [])
|
|
566
|
+
{
|
|
567
|
+
parent::__construct();
|
|
568
|
+
$this->query = new FleetOpsTrackOrderQueryFake($orders);
|
|
569
|
+
$this->progressBar = new FleetOpsTrackProgressBarFake();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
public function option($key = null)
|
|
573
|
+
{
|
|
574
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
public function info($string, $verbosity = null)
|
|
578
|
+
{
|
|
579
|
+
$this->messages[] = ['info', $string];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
public function warn($string, $verbosity = null)
|
|
583
|
+
{
|
|
584
|
+
$this->messages[] = ['warn', $string];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
public function error($string, $verbosity = null)
|
|
588
|
+
{
|
|
589
|
+
$this->messages[] = ['error', $string];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
public function alert($string, $verbosity = null)
|
|
593
|
+
{
|
|
594
|
+
$this->messages[] = ['alert', $string];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
public function newLine($count = 1)
|
|
598
|
+
{
|
|
599
|
+
$this->messages[] = ['newLine', $count];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
protected function activeOrdersQuery(Carbon $cutoff)
|
|
603
|
+
{
|
|
604
|
+
$this->messages[] = ['activeOrdersQuery', $cutoff->toDateTimeString()];
|
|
605
|
+
|
|
606
|
+
return $this->query;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
protected function createProgressBar(int $total)
|
|
610
|
+
{
|
|
611
|
+
$this->messages[] = ['createProgressBar', $total];
|
|
612
|
+
|
|
613
|
+
return $this->progressBar;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
class FleetOpsTrackProgressBarFake
|
|
618
|
+
{
|
|
619
|
+
public int $started = 0;
|
|
620
|
+
public int $advanced = 0;
|
|
621
|
+
public int $finished = 0;
|
|
622
|
+
|
|
623
|
+
public function start(): void
|
|
624
|
+
{
|
|
625
|
+
$this->started++;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
public function advance(): void
|
|
629
|
+
{
|
|
630
|
+
$this->advanced++;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
public function finish(): void
|
|
634
|
+
{
|
|
635
|
+
$this->finished++;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
class FleetOpsTrackOrderQueryFake
|
|
640
|
+
{
|
|
641
|
+
public array $calls = [];
|
|
642
|
+
|
|
643
|
+
public function __construct(private array $orders)
|
|
644
|
+
{
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
public function __clone()
|
|
648
|
+
{
|
|
649
|
+
$this->calls[] = ['clone'];
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
public function count($columns = '*'): int
|
|
653
|
+
{
|
|
654
|
+
$this->calls[] = ['count', $columns];
|
|
655
|
+
|
|
656
|
+
return count($this->orders);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
public function orderBy(string $column): self
|
|
660
|
+
{
|
|
661
|
+
$this->calls[] = ['orderBy', $column];
|
|
662
|
+
|
|
663
|
+
return $this;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
public function chunkById(int $perChunk, Closure $callback): void
|
|
667
|
+
{
|
|
668
|
+
$this->calls[] = ['chunkById', $perChunk];
|
|
669
|
+
|
|
670
|
+
$callback(new FleetOpsTrackOrderChunkFake($this->orders));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
class FleetOpsFixLegacyOrderConfigsCommandFake extends FixLegacyOrderConfigs
|
|
675
|
+
{
|
|
676
|
+
public array $messages = [];
|
|
677
|
+
public array $created = [];
|
|
678
|
+
public array $configs = [];
|
|
679
|
+
public Illuminate\Support\Collection $testCompanies;
|
|
680
|
+
public Illuminate\Support\Collection $testOrders;
|
|
681
|
+
public FleetOpsTrackProgressBarFake $progressBar;
|
|
682
|
+
|
|
683
|
+
public function __construct(private bool $createConfigs)
|
|
684
|
+
{
|
|
685
|
+
parent::__construct();
|
|
686
|
+
$this->testCompanies = collect();
|
|
687
|
+
$this->testOrders = collect();
|
|
688
|
+
$this->progressBar = new FleetOpsTrackProgressBarFake();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
public function option($key = null)
|
|
692
|
+
{
|
|
693
|
+
$options = ['create-configs' => $this->createConfigs];
|
|
694
|
+
|
|
695
|
+
return $key === null ? $options : ($options[$key] ?? null);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
public function info($string, $verbosity = null)
|
|
699
|
+
{
|
|
700
|
+
$this->messages[] = ['info', $string];
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
public function line($string, $style = null, $verbosity = null)
|
|
704
|
+
{
|
|
705
|
+
$this->messages[] = ['line', $string];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
public function error($string, $verbosity = null)
|
|
709
|
+
{
|
|
710
|
+
$this->messages[] = ['error', $string];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
protected function companies()
|
|
714
|
+
{
|
|
715
|
+
return $this->testCompanies;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
protected function createTransportConfig(Company $company): void
|
|
719
|
+
{
|
|
720
|
+
$this->created[] = $company->uuid;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
protected function ordersWithoutConfig()
|
|
724
|
+
{
|
|
725
|
+
return $this->testOrders;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
protected function transportConfigForCompany(string $companyUuid): ?OrderConfig
|
|
729
|
+
{
|
|
730
|
+
return $this->configs[$companyUuid] ?? null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
protected function createProgressBar(int $total)
|
|
734
|
+
{
|
|
735
|
+
$this->messages[] = ['createProgressBar', $total];
|
|
736
|
+
|
|
737
|
+
return $this->progressBar;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
class FleetOpsLegacyOrderFake extends Order
|
|
742
|
+
{
|
|
743
|
+
public array $updates = [];
|
|
744
|
+
public bool $failUpdate = false;
|
|
745
|
+
|
|
746
|
+
public function update(array $attributes = [], array $options = [])
|
|
747
|
+
{
|
|
748
|
+
if ($this->failUpdate) {
|
|
749
|
+
throw new RuntimeException('order update failed');
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
$this->updates[] = $attributes;
|
|
753
|
+
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
class FleetOpsFixCustomerCompaniesCommandFake extends FixCustomerCompanies
|
|
759
|
+
{
|
|
760
|
+
public array $messages = [];
|
|
761
|
+
public array $users = [];
|
|
762
|
+
public array $missing = [];
|
|
763
|
+
public array $companies = [];
|
|
764
|
+
public array $customerUsers = [];
|
|
765
|
+
public Illuminate\Support\Collection $testCustomers;
|
|
766
|
+
|
|
767
|
+
public function __construct()
|
|
768
|
+
{
|
|
769
|
+
parent::__construct();
|
|
770
|
+
$this->testCustomers = collect();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
public function info($string, $verbosity = null)
|
|
774
|
+
{
|
|
775
|
+
$this->messages[] = ['info', $string];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
public function line($string, $style = null, $verbosity = null)
|
|
779
|
+
{
|
|
780
|
+
$this->messages[] = ['line', $string];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
public function error($string, $verbosity = null)
|
|
784
|
+
{
|
|
785
|
+
$this->messages[] = ['error', $string];
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
protected function customers()
|
|
789
|
+
{
|
|
790
|
+
return $this->testCustomers;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
protected function customerUser(Contact $customer)
|
|
794
|
+
{
|
|
795
|
+
return $this->customerUsers[spl_object_id($customer)] ?? null;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
protected function createUserForCustomer(Contact $customer): mixed
|
|
799
|
+
{
|
|
800
|
+
if ($customer->failCreate ?? false) {
|
|
801
|
+
throw new RuntimeException('existing user');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
$this->customerUsers[spl_object_id($customer)] = $customer->createdUser;
|
|
805
|
+
|
|
806
|
+
return $customer->createdUser;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
protected function assignExistingUserToCustomer(Contact $customer, $existingUser): void
|
|
810
|
+
{
|
|
811
|
+
$customer->updateQuietly(['user_uuid' => $existingUser->uuid]);
|
|
812
|
+
$this->customerUsers[spl_object_id($customer)] = $existingUser;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
protected function userByEmail(string $email)
|
|
816
|
+
{
|
|
817
|
+
return $this->users[$email] ?? null;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
protected function missingCompanyUser(string $userUuid, string $companyUuid): bool
|
|
821
|
+
{
|
|
822
|
+
return $this->missing[$userUuid . ':' . $companyUuid] ?? false;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
protected function companyByUuid(string $companyUuid): ?Company
|
|
826
|
+
{
|
|
827
|
+
return $this->companies[$companyUuid] ?? null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
class FleetOpsFixDriverCompaniesCommandFake extends FixDriverCompanies
|
|
832
|
+
{
|
|
833
|
+
public array $messages = [];
|
|
834
|
+
public array $missing = [];
|
|
835
|
+
public array $companies = [];
|
|
836
|
+
public Illuminate\Support\Collection $testDrivers;
|
|
837
|
+
|
|
838
|
+
public function __construct()
|
|
839
|
+
{
|
|
840
|
+
parent::__construct();
|
|
841
|
+
$this->testDrivers = collect();
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
public function line($string, $style = null, $verbosity = null)
|
|
845
|
+
{
|
|
846
|
+
$this->messages[] = ['line', $string];
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
protected function drivers()
|
|
850
|
+
{
|
|
851
|
+
return $this->testDrivers;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
protected function missingCompanyUser(string $userUuid, string $companyUuid): bool
|
|
855
|
+
{
|
|
856
|
+
return $this->missing[$userUuid . ':' . $companyUuid] ?? false;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
protected function companyByUuid(string $companyUuid): ?Company
|
|
860
|
+
{
|
|
861
|
+
return $this->companies[$companyUuid] ?? null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
class FleetOpsUserCommandFake
|
|
866
|
+
{
|
|
867
|
+
public string $uuid;
|
|
868
|
+
public string $name;
|
|
869
|
+
public string $email;
|
|
870
|
+
public array $synced = [];
|
|
871
|
+
public array $assigned = [];
|
|
872
|
+
|
|
873
|
+
public function __construct(string $uuid, string $name, string $email)
|
|
874
|
+
{
|
|
875
|
+
$this->uuid = $uuid;
|
|
876
|
+
$this->name = $name;
|
|
877
|
+
$this->email = $email;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
public function syncProperty(string $property, Model $model): bool
|
|
881
|
+
{
|
|
882
|
+
$this->synced[] = [$property, $model::class];
|
|
883
|
+
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
public function assignCompany(Company $company, string $role = 'Administrator'): self
|
|
888
|
+
{
|
|
889
|
+
$this->assigned[] = [$company->uuid, $role];
|
|
890
|
+
|
|
891
|
+
return $this;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
class FleetOpsCustomerCommandFake extends Contact
|
|
896
|
+
{
|
|
897
|
+
public mixed $createdUser = null;
|
|
898
|
+
public bool $failCreate = false;
|
|
899
|
+
public array $quietUpdates = [];
|
|
900
|
+
|
|
901
|
+
public function loadMissing($relations)
|
|
902
|
+
{
|
|
903
|
+
return $this;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
public function updateQuietly(array $attributes = [], array $options = [])
|
|
907
|
+
{
|
|
908
|
+
$this->quietUpdates[] = $attributes;
|
|
909
|
+
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
class FleetOpsPurgeServiceQuotesCommandFake extends PurgeUnpurchasedServiceQuotes
|
|
915
|
+
{
|
|
916
|
+
public array $messages = [];
|
|
917
|
+
public array $events = [];
|
|
918
|
+
public int $deletedCount = 0;
|
|
919
|
+
public ?Throwable $failure = null;
|
|
920
|
+
|
|
921
|
+
public function info($string, $verbosity = null)
|
|
922
|
+
{
|
|
923
|
+
$this->messages[] = ['info', $string];
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
public function error($string, $verbosity = null)
|
|
927
|
+
{
|
|
928
|
+
$this->messages[] = ['error', $string];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
protected function disableForeignKeyConstraints(): void
|
|
932
|
+
{
|
|
933
|
+
$this->events[] = 'disable';
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
protected function enableForeignKeyConstraints(): void
|
|
937
|
+
{
|
|
938
|
+
$this->events[] = 'enable';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
protected function beginTransaction(): void
|
|
942
|
+
{
|
|
943
|
+
$this->events[] = 'begin';
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
protected function commit(): void
|
|
947
|
+
{
|
|
948
|
+
$this->events[] = 'commit';
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
protected function rollBack(): void
|
|
952
|
+
{
|
|
953
|
+
$this->events[] = 'rollback';
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
protected function purgeServiceQuotes($thresholdDate): int
|
|
957
|
+
{
|
|
958
|
+
$this->events[] = ['purge', $thresholdDate->copy()];
|
|
959
|
+
|
|
960
|
+
if ($this->failure) {
|
|
961
|
+
throw $this->failure;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return $this->deletedCount;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
class FleetOpsSendDriverNotificationCommandFake extends SendDriverNotification
|
|
969
|
+
{
|
|
970
|
+
public array $messages = [];
|
|
971
|
+
public array $questions = [];
|
|
972
|
+
public array $choices = [];
|
|
973
|
+
public array $sent = [];
|
|
974
|
+
public ?Order $order = null;
|
|
975
|
+
public object $matrix;
|
|
976
|
+
|
|
977
|
+
public function __construct(private array $testOptions)
|
|
978
|
+
{
|
|
979
|
+
parent::__construct();
|
|
980
|
+
$this->matrix = (object) ['distance' => 1234];
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
public function option($key = null)
|
|
984
|
+
{
|
|
985
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
public function ask($question, $default = null)
|
|
989
|
+
{
|
|
990
|
+
$this->questions[] = [$question, $default];
|
|
991
|
+
|
|
992
|
+
return 'asked_order';
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
public function choice($question, array $choices, $default = null, $attempts = null, $multiple = false)
|
|
996
|
+
{
|
|
997
|
+
$this->choices[] = [$question, $choices, $default, $attempts, $multiple];
|
|
998
|
+
|
|
999
|
+
return 'assigned';
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
public function info($string, $verbosity = null)
|
|
1003
|
+
{
|
|
1004
|
+
$this->messages[] = ['info', $string];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
public function error($string, $verbosity = null)
|
|
1008
|
+
{
|
|
1009
|
+
$this->messages[] = ['error', $string];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
protected function findOrder(string $orderId): ?Order
|
|
1013
|
+
{
|
|
1014
|
+
$this->messages[] = ['findOrder', $orderId];
|
|
1015
|
+
|
|
1016
|
+
return $this->order;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
protected function calculateDrivingDistanceAndTime(mixed $origin, mixed $destination): object
|
|
1020
|
+
{
|
|
1021
|
+
$this->messages[] = ['distance', $origin, $destination];
|
|
1022
|
+
|
|
1023
|
+
return $this->matrix;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
protected function notifyDriver($driver, string $notificationClass, Order $order, mixed $distance = null): void
|
|
1027
|
+
{
|
|
1028
|
+
$this->sent[] = [$driver, $notificationClass, $order->public_id, $distance];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
class FleetOpsNotificationDriverCommandFake extends Driver
|
|
1033
|
+
{
|
|
1034
|
+
public array $notifications = [];
|
|
1035
|
+
public mixed $locationForTest = null;
|
|
1036
|
+
|
|
1037
|
+
public function getAttribute($key)
|
|
1038
|
+
{
|
|
1039
|
+
if ($key === 'location') {
|
|
1040
|
+
return $this->locationForTest;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
return parent::getAttribute($key);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
public function notify($instance): void
|
|
1047
|
+
{
|
|
1048
|
+
$this->notifications[] = $instance;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
class FleetOpsNotificationOrderCommandFake extends Order
|
|
1053
|
+
{
|
|
1054
|
+
public string $publicIdForTest = 'order_public';
|
|
1055
|
+
public mixed $driverAssignedForTest = null;
|
|
1056
|
+
public mixed $payloadForTest = null;
|
|
1057
|
+
|
|
1058
|
+
public function loadMissing($relations)
|
|
1059
|
+
{
|
|
1060
|
+
return $this;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
public function getAttribute($key)
|
|
1064
|
+
{
|
|
1065
|
+
return match ($key) {
|
|
1066
|
+
'public_id' => $this->publicIdForTest,
|
|
1067
|
+
'driverAssigned' => $this->driverAssignedForTest,
|
|
1068
|
+
'payload' => $this->payloadForTest,
|
|
1069
|
+
default => parent::getAttribute($key),
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
class FleetOpsNotificationPayloadCommandFake
|
|
1075
|
+
{
|
|
1076
|
+
public function __construct(private mixed $pickup)
|
|
1077
|
+
{
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
public function getPickupOrFirstWaypoint(): mixed
|
|
1081
|
+
{
|
|
1082
|
+
return $this->pickup;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
class FleetOpsDispatchOrdersCommandFake extends DispatchOrders
|
|
1087
|
+
{
|
|
1088
|
+
public array $messages = [];
|
|
1089
|
+
public EloquentCollection $orders;
|
|
1090
|
+
|
|
1091
|
+
public function __construct(private array $testOptions)
|
|
1092
|
+
{
|
|
1093
|
+
parent::__construct();
|
|
1094
|
+
$this->orders = new EloquentCollection();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
public function option($key = null)
|
|
1098
|
+
{
|
|
1099
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
public function info($string, $verbosity = null)
|
|
1103
|
+
{
|
|
1104
|
+
$this->messages[] = ['info', $string];
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
public function alert($string, $verbosity = null)
|
|
1108
|
+
{
|
|
1109
|
+
$this->messages[] = ['alert', $string];
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
public function warn($string, $verbosity = null)
|
|
1113
|
+
{
|
|
1114
|
+
$this->messages[] = ['warn', $string];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
protected function getScheduledOrders(bool $sandboxMode): EloquentCollection
|
|
1118
|
+
{
|
|
1119
|
+
$this->messages[] = ['getScheduledOrders', $sandboxMode];
|
|
1120
|
+
|
|
1121
|
+
return $this->orders;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
class FleetOpsScheduledOrderCommandFake extends Order
|
|
1126
|
+
{
|
|
1127
|
+
public string $publicIdForTest = '';
|
|
1128
|
+
public string $scheduledAtForTest = '';
|
|
1129
|
+
public bool $ready = false;
|
|
1130
|
+
public bool $dispatchedForTest = false;
|
|
1131
|
+
|
|
1132
|
+
public function getAttribute($key)
|
|
1133
|
+
{
|
|
1134
|
+
return match ($key) {
|
|
1135
|
+
'public_id' => $this->publicIdForTest,
|
|
1136
|
+
'scheduled_at' => $this->scheduledAtForTest,
|
|
1137
|
+
default => parent::getAttribute($key),
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
public function shouldDispatch($precision = 1)
|
|
1142
|
+
{
|
|
1143
|
+
return $this->ready;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
public function dispatch(bool $save = true): self
|
|
1147
|
+
{
|
|
1148
|
+
$this->dispatchedForTest = true;
|
|
1149
|
+
|
|
1150
|
+
return $this;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
class FleetOpsAssignDriverRolesCommandFake extends AssignDriverRoles
|
|
1155
|
+
{
|
|
1156
|
+
public array $messages = [];
|
|
1157
|
+
public Illuminate\Support\Collection $testCompanies;
|
|
1158
|
+
|
|
1159
|
+
public function __construct()
|
|
1160
|
+
{
|
|
1161
|
+
parent::__construct();
|
|
1162
|
+
$this->testCompanies = collect();
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
public function info($string, $verbosity = null)
|
|
1166
|
+
{
|
|
1167
|
+
$this->messages[] = ['info', $string];
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
public function error($string, $verbosity = null)
|
|
1171
|
+
{
|
|
1172
|
+
$this->messages[] = ['error', $string];
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
protected function companies()
|
|
1176
|
+
{
|
|
1177
|
+
return $this->testCompanies;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
protected function isUser($user): bool
|
|
1181
|
+
{
|
|
1182
|
+
return $user instanceof FleetOpsRoleUserCommandFake;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
protected function setCompanyUserRelation($user, Company $company): void
|
|
1186
|
+
{
|
|
1187
|
+
$user->companies[] = $company->uuid;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
protected function driverForCompany($user, string $companyUuid)
|
|
1191
|
+
{
|
|
1192
|
+
return $user->drivers[$companyUuid] ?? null;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
protected function isNotAdmin($user): bool
|
|
1196
|
+
{
|
|
1197
|
+
return !$user->admin;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
protected function assignDriverRole($user): void
|
|
1201
|
+
{
|
|
1202
|
+
$user->assignSingleRole('Driver');
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
class FleetOpsAssignCustomerRolesCommandFake extends AssignCustomerRoles
|
|
1207
|
+
{
|
|
1208
|
+
public array $messages = [];
|
|
1209
|
+
public array $customerUsers = [];
|
|
1210
|
+
public Illuminate\Support\Collection $testCustomers;
|
|
1211
|
+
|
|
1212
|
+
public function __construct()
|
|
1213
|
+
{
|
|
1214
|
+
parent::__construct();
|
|
1215
|
+
$this->testCustomers = collect();
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
public function info($string, $verbosity = null)
|
|
1219
|
+
{
|
|
1220
|
+
$this->messages[] = ['info', $string];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
public function error($string, $verbosity = null)
|
|
1224
|
+
{
|
|
1225
|
+
$this->messages[] = ['error', $string];
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
protected function customers()
|
|
1229
|
+
{
|
|
1230
|
+
return $this->testCustomers;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
protected function customerUser(Contact $customer)
|
|
1234
|
+
{
|
|
1235
|
+
return $this->customerUsers[spl_object_id($customer)] ?? null;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
protected function createUserForCustomer(Contact $customer)
|
|
1239
|
+
{
|
|
1240
|
+
$this->customerUsers[spl_object_id($customer)] = $customer->createdUser;
|
|
1241
|
+
|
|
1242
|
+
return $customer->createdUser;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
protected function assignCustomerRole($user): void
|
|
1246
|
+
{
|
|
1247
|
+
$user->assignSingleRole('Fleet-Ops Customer');
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
class FleetOpsRoleUserCommandFake
|
|
1252
|
+
{
|
|
1253
|
+
public string $email;
|
|
1254
|
+
public bool $admin = false;
|
|
1255
|
+
public array $roles = [];
|
|
1256
|
+
public array $companies = [];
|
|
1257
|
+
public array $drivers = [];
|
|
1258
|
+
public ?Throwable $failure = null;
|
|
1259
|
+
|
|
1260
|
+
public function __construct(string $email)
|
|
1261
|
+
{
|
|
1262
|
+
$this->email = $email;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
public function assignSingleRole(string $role): void
|
|
1266
|
+
{
|
|
1267
|
+
if ($this->failure) {
|
|
1268
|
+
throw $this->failure;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
$this->roles[] = $role;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
class FleetOpsCustomerRoleCommandFake extends Contact
|
|
1276
|
+
{
|
|
1277
|
+
public mixed $createdUser = null;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
class FleetOpsTrackOrderChunkFake implements IteratorAggregate
|
|
1281
|
+
{
|
|
1282
|
+
public array $loaded = [];
|
|
1283
|
+
|
|
1284
|
+
public function __construct(private array $orders)
|
|
1285
|
+
{
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
public function load(array $relations): self
|
|
1289
|
+
{
|
|
1290
|
+
$this->loaded = $relations;
|
|
1291
|
+
|
|
1292
|
+
return $this;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
public function getIterator(): Traversable
|
|
1296
|
+
{
|
|
1297
|
+
return new ArrayIterator($this->orders);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
class FleetOpsTrackOrderFake extends Order
|
|
1302
|
+
{
|
|
1303
|
+
public int $id;
|
|
1304
|
+
public array $distanceCalls = [];
|
|
1305
|
+
|
|
1306
|
+
public function __construct(int $id = 0, private bool $shouldFail = false)
|
|
1307
|
+
{
|
|
1308
|
+
parent::__construct();
|
|
1309
|
+
$this->id = $id;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
public function setDistanceAndTime(array $options = []): Order
|
|
1313
|
+
{
|
|
1314
|
+
if ($this->shouldFail) {
|
|
1315
|
+
throw new RuntimeException('distance provider failed');
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
$this->distanceCalls[] = $options;
|
|
1319
|
+
|
|
1320
|
+
return $this;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
class FleetOpsAuditCustomerUserConflictsProbe extends AuditCustomerUserConflicts
|
|
1325
|
+
{
|
|
1326
|
+
public array $messages = [];
|
|
1327
|
+
public array $tables = [];
|
|
1328
|
+
public FleetOpsAuditCustomerQueryFake $query;
|
|
1329
|
+
|
|
1330
|
+
public function __construct(private array $testOptions, array $contacts)
|
|
1331
|
+
{
|
|
1332
|
+
parent::__construct();
|
|
1333
|
+
$this->query = new FleetOpsAuditCustomerQueryFake($contacts);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
public function option($key = null)
|
|
1337
|
+
{
|
|
1338
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
public function line($string, $style = null, $verbosity = null)
|
|
1342
|
+
{
|
|
1343
|
+
$this->messages[] = ['line', $string];
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
public function info($string, $verbosity = null)
|
|
1347
|
+
{
|
|
1348
|
+
$this->messages[] = ['info', $string];
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
public function table($headers, $rows, $tableStyle = 'default', array $columnStyles = [])
|
|
1352
|
+
{
|
|
1353
|
+
$this->tables[] = [$headers, $rows];
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
protected function customerContactsQuery()
|
|
1357
|
+
{
|
|
1358
|
+
return $this->query;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
class FleetOpsAuditCustomerQueryFake
|
|
1363
|
+
{
|
|
1364
|
+
public array $calls = [];
|
|
1365
|
+
|
|
1366
|
+
public function __construct(private array $contacts)
|
|
1367
|
+
{
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
public function where(string $column, mixed $value): self
|
|
1371
|
+
{
|
|
1372
|
+
$this->calls[] = ['where', $column, $value];
|
|
1373
|
+
|
|
1374
|
+
return $this;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
public function get(): Illuminate\Support\Collection
|
|
1378
|
+
{
|
|
1379
|
+
$this->calls[] = ['get'];
|
|
1380
|
+
|
|
1381
|
+
return collect($this->contacts);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function fleetOpsAuditContact(array $attributes, mixed $user, ?object $company = null): Contact
|
|
1386
|
+
{
|
|
1387
|
+
$contact = new Contact();
|
|
1388
|
+
$contact->setRawAttributes(array_merge([
|
|
1389
|
+
'uuid' => 'contact-uuid',
|
|
1390
|
+
'public_id' => 'contact_public',
|
|
1391
|
+
'name' => 'Jane Customer',
|
|
1392
|
+
'email' => 'jane@example.test',
|
|
1393
|
+
'company_uuid' => 'company-uuid',
|
|
1394
|
+
'updated_at' => Carbon::parse('2026-07-26 12:00:00'),
|
|
1395
|
+
], $attributes), true);
|
|
1396
|
+
$contact->setRelation('anyUser', $user);
|
|
1397
|
+
$contact->setRelation('company', $company ?? (object) ['name' => 'Acme Logistics']);
|
|
1398
|
+
|
|
1399
|
+
return $contact;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function fleetOpsSyncTelematicsCommandWithOptions(array $options = []): SyncTelematics
|
|
1403
|
+
{
|
|
1404
|
+
return new class($options) extends SyncTelematics {
|
|
1405
|
+
public array $messages = [];
|
|
1406
|
+
|
|
1407
|
+
public function __construct(private array $testOptions)
|
|
1408
|
+
{
|
|
1409
|
+
parent::__construct();
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
public function option($key = null)
|
|
1413
|
+
{
|
|
1414
|
+
return $key === null ? $this->testOptions : ($this->testOptions[$key] ?? null);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
public function info($string, $verbosity = null)
|
|
1418
|
+
{
|
|
1419
|
+
$this->messages[] = ['info', $string];
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
public function warn($string, $verbosity = null)
|
|
1423
|
+
{
|
|
1424
|
+
$this->messages[] = ['warn', $string];
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function fleetOpsReminderSchedule(string $uuid, string $publicId, string $name, string $nextDueDate, array $offsets, ?string $email): MaintenanceSchedule
|
|
1430
|
+
{
|
|
1431
|
+
$schedule = (new ReflectionClass(MaintenanceSchedule::class))->newInstanceWithoutConstructor();
|
|
1432
|
+
$schedule->setRawAttributes([
|
|
1433
|
+
'uuid' => $uuid,
|
|
1434
|
+
'public_id' => $publicId,
|
|
1435
|
+
'name' => $name,
|
|
1436
|
+
'next_due_date' => Carbon::parse($nextDueDate),
|
|
1437
|
+
'reminder_offsets' => json_encode($offsets),
|
|
1438
|
+
'default_assignee_uuid' => $email ? $uuid . '-assignee' : null,
|
|
1439
|
+
], true);
|
|
1440
|
+
|
|
1441
|
+
$schedule->setRelation('defaultAssignee', $email ? (object) ['email' => $email] : null);
|
|
1442
|
+
|
|
1443
|
+
return $schedule;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function fleetOpsMaintenanceTriggerSchedule(string $uuid, string $publicId, string $name, string $nextDueDate, int $nextOdometer, int $nextEngineHours, Vehicle $subject): MaintenanceSchedule
|
|
1447
|
+
{
|
|
1448
|
+
$schedule = (new ReflectionClass(MaintenanceSchedule::class))->newInstanceWithoutConstructor();
|
|
1449
|
+
$schedule->setRawAttributes([
|
|
1450
|
+
'uuid' => $uuid,
|
|
1451
|
+
'public_id' => $publicId,
|
|
1452
|
+
'company_uuid' => 'company-uuid',
|
|
1453
|
+
'subject_type' => Vehicle::class,
|
|
1454
|
+
'subject_uuid' => $subject->uuid,
|
|
1455
|
+
'name' => $name,
|
|
1456
|
+
'status' => 'active',
|
|
1457
|
+
'next_due_date' => Carbon::parse($nextDueDate),
|
|
1458
|
+
'next_due_odometer' => $nextOdometer,
|
|
1459
|
+
'next_due_engine_hours' => $nextEngineHours,
|
|
1460
|
+
'default_priority' => null,
|
|
1461
|
+
'default_assignee_type' => 'user',
|
|
1462
|
+
'default_assignee_uuid' => 'assignee-uuid',
|
|
1463
|
+
'instructions' => 'Inspect brakes and tires.',
|
|
1464
|
+
], true);
|
|
1465
|
+
$schedule->setRelation('subject', $subject);
|
|
1466
|
+
|
|
1467
|
+
return $schedule;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function fleetOpsSimulationDriver(string $uuid = 'driver-uuid', string $publicId = 'driver_public', string $name = 'Jane Driver'): Driver
|
|
1471
|
+
{
|
|
1472
|
+
$driver = new FleetOpsDispatchAdhocOrdersDriverFake();
|
|
1473
|
+
$driver->uuid = $uuid;
|
|
1474
|
+
$driver->public_id = $publicId;
|
|
1475
|
+
$driver->name = $name;
|
|
1476
|
+
|
|
1477
|
+
return $driver;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function fleetOpsSimulationOrder(?Driver $assignedDriver = null): FleetOpsSimulateOrderFake
|
|
1481
|
+
{
|
|
1482
|
+
$order = (new ReflectionClass(FleetOpsSimulateOrderFake::class))->newInstanceWithoutConstructor();
|
|
1483
|
+
$order->setRawAttributes([
|
|
1484
|
+
'uuid' => 'order-uuid',
|
|
1485
|
+
'public_id' => 'order_public',
|
|
1486
|
+
], true);
|
|
1487
|
+
$order->assignedDriver = $assignedDriver;
|
|
1488
|
+
$order->setRelation('payload', new class {
|
|
1489
|
+
public function getPickupOrFirstWaypoint(): object
|
|
1490
|
+
{
|
|
1491
|
+
return (object) [
|
|
1492
|
+
'address' => '1 Pickup Street',
|
|
1493
|
+
'location' => new Point(1.30, 103.80),
|
|
1494
|
+
];
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
public function getDropoffOrLastWaypoint(): object
|
|
1498
|
+
{
|
|
1499
|
+
return (object) [
|
|
1500
|
+
'address' => '99 Dropoff Avenue',
|
|
1501
|
+
'location' => new Point(1.32, 103.82),
|
|
1502
|
+
];
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
return $order;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
test('sync telematics exits cleanly when another process holds the lock', function () {
|
|
1510
|
+
$lock = new FleetOpsCommandLockFake(false);
|
|
1511
|
+
Cache::swap(new FleetOpsCommandCacheFake($lock));
|
|
1512
|
+
|
|
1513
|
+
$registry = new FleetOpsTelematicProviderRegistryFake(collect());
|
|
1514
|
+
$command = fleetOpsSyncTelematicsCommandWithOptions(['no-lock' => false]);
|
|
1515
|
+
|
|
1516
|
+
expect($command->handle($registry))->toBe(Command::SUCCESS)
|
|
1517
|
+
->and($command->messages)->toContain(['warn', 'Another telematics sync run appears to be in progress.'])
|
|
1518
|
+
->and($lock->released)->toBeFalse();
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
test('sync telematics reports no pollable providers after provider filtering', function () {
|
|
1522
|
+
$registry = new FleetOpsTelematicProviderRegistryFake(collect([
|
|
1523
|
+
'webhook' => new TelematicProviderDescriptor([
|
|
1524
|
+
'key' => 'webhook',
|
|
1525
|
+
'label' => 'Webhook Provider',
|
|
1526
|
+
'supports_webhooks' => true,
|
|
1527
|
+
'supports_discovery' => true,
|
|
1528
|
+
]),
|
|
1529
|
+
'manual' => new TelematicProviderDescriptor([
|
|
1530
|
+
'key' => 'manual',
|
|
1531
|
+
'label' => 'Manual Provider',
|
|
1532
|
+
'supports_discovery' => false,
|
|
1533
|
+
]),
|
|
1534
|
+
]));
|
|
1535
|
+
|
|
1536
|
+
$command = fleetOpsSyncTelematicsCommandWithOptions([
|
|
1537
|
+
'no-lock' => true,
|
|
1538
|
+
'provider' => [],
|
|
1539
|
+
'exclude-webhook-providers' => true,
|
|
1540
|
+
]);
|
|
1541
|
+
|
|
1542
|
+
expect($command->handle($registry))->toBe(Command::SUCCESS)
|
|
1543
|
+
->and($command->messages)->toContain(['info', 'No pollable telematics providers found.']);
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
test('sync telematics filters requested pollable providers', function () {
|
|
1547
|
+
$registry = new FleetOpsTelematicProviderRegistryFake(collect([
|
|
1548
|
+
'afaqy' => new TelematicProviderDescriptor([
|
|
1549
|
+
'key' => 'afaqy',
|
|
1550
|
+
'label' => 'Afaqy',
|
|
1551
|
+
'supports_discovery' => true,
|
|
1552
|
+
]),
|
|
1553
|
+
'samsara' => new TelematicProviderDescriptor([
|
|
1554
|
+
'key' => 'samsara',
|
|
1555
|
+
'label' => 'Samsara',
|
|
1556
|
+
'supports_discovery' => true,
|
|
1557
|
+
]),
|
|
1558
|
+
'webhook_only' => new TelematicProviderDescriptor([
|
|
1559
|
+
'key' => 'webhook_only',
|
|
1560
|
+
'label' => 'Webhook Only',
|
|
1561
|
+
'supports_webhooks' => true,
|
|
1562
|
+
'supports_discovery' => true,
|
|
1563
|
+
]),
|
|
1564
|
+
]));
|
|
1565
|
+
|
|
1566
|
+
$command = fleetOpsSyncTelematicsCommandWithOptions([
|
|
1567
|
+
'provider' => ['samsara', 'webhook_only'],
|
|
1568
|
+
'exclude-webhook-providers' => true,
|
|
1569
|
+
]);
|
|
1570
|
+
|
|
1571
|
+
$method = new ReflectionMethod($command, 'pollableProviderKeys');
|
|
1572
|
+
$method->setAccessible(true);
|
|
1573
|
+
|
|
1574
|
+
expect($method->invoke($command, $registry))->toBe(['samsara']);
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
test('debug order tracker command exits successfully', function () {
|
|
1578
|
+
expect((new DebugOrderTracker())->handle())->toBe(Command::SUCCESS);
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
test('process maintenance triggers exposes deterministic command helpers', function () {
|
|
1582
|
+
Carbon::setTestNow(Carbon::parse('2026-02-03 04:05:06'));
|
|
1583
|
+
|
|
1584
|
+
$command = new FleetOpsProcessMaintenanceTriggersProbe();
|
|
1585
|
+
$schedule = (object) [
|
|
1586
|
+
'next_due_date' => Carbon::parse('2026-02-01'),
|
|
1587
|
+
'next_due_odometer' => 12000,
|
|
1588
|
+
'next_due_engine_hours' => 300,
|
|
1589
|
+
];
|
|
1590
|
+
|
|
1591
|
+
$vehicle = new Vehicle();
|
|
1592
|
+
$vehicle->odometer = 12500;
|
|
1593
|
+
$vehicle->engine_hours = 450;
|
|
1594
|
+
|
|
1595
|
+
expect($command->callHelper('connectionName', true))->toBe('sandbox')
|
|
1596
|
+
->and($command->callHelper('connectionName', false))->toBe('mysql')
|
|
1597
|
+
->and($command->callHelper('currentReadingsFromSubject', $vehicle))->toBe([12500, 450])
|
|
1598
|
+
->and($command->callHelper('currentReadingsFromSubject', new stdClass()))->toBe([null, null])
|
|
1599
|
+
->and($command->callHelper('triggerReasons', $schedule, 12500, 450))->toBe([
|
|
1600
|
+
'date due 2026-02-01',
|
|
1601
|
+
'odometer 12500 >= 12000',
|
|
1602
|
+
'engine hours 450 >= 300',
|
|
1603
|
+
])
|
|
1604
|
+
->and($command->callHelper('triggerReasons', $schedule, 11000, 250))->toBe([
|
|
1605
|
+
'date due 2026-02-01',
|
|
1606
|
+
])
|
|
1607
|
+
->and($command->callHelper('workOrderCode', 7, Carbon::parse('2026-02-03')))->toBe('WO-20260203-0007')
|
|
1608
|
+
->and($command->callHelper('processedSummary', 2, false))->toBe('Processed 2 schedule trigger(s).')
|
|
1609
|
+
->and($command->callHelper('processedSummary', 2, true))->toBe('Processed 2 schedule trigger(s) (dry run — no work orders created)');
|
|
1610
|
+
|
|
1611
|
+
Carbon::setTestNow();
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
test('process maintenance triggers handles dry run create and duplicate branches', function () {
|
|
1615
|
+
Carbon::setTestNow(Carbon::parse('2026-02-03 04:05:06'));
|
|
1616
|
+
|
|
1617
|
+
$vehicle = new Vehicle();
|
|
1618
|
+
$vehicle->uuid = 'vehicle-uuid';
|
|
1619
|
+
$vehicle->odometer = 12500;
|
|
1620
|
+
$vehicle->engine_hours = 450;
|
|
1621
|
+
|
|
1622
|
+
$due = fleetOpsMaintenanceTriggerSchedule(
|
|
1623
|
+
'schedule-due',
|
|
1624
|
+
'schedule_public',
|
|
1625
|
+
'Brake Service',
|
|
1626
|
+
'2026-02-01',
|
|
1627
|
+
12000,
|
|
1628
|
+
300,
|
|
1629
|
+
$vehicle
|
|
1630
|
+
);
|
|
1631
|
+
$notDue = fleetOpsMaintenanceTriggerSchedule(
|
|
1632
|
+
'schedule-not-due',
|
|
1633
|
+
'schedule_future',
|
|
1634
|
+
'Future Service',
|
|
1635
|
+
'2026-03-01',
|
|
1636
|
+
13000,
|
|
1637
|
+
600,
|
|
1638
|
+
$vehicle
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
$dryRun = new FleetOpsProcessMaintenanceTriggersHandleFake(['sandbox' => true, 'dry-run' => true]);
|
|
1642
|
+
$dryRun->testSchedules = collect([$due, $notDue]);
|
|
1643
|
+
$dryRun->handle();
|
|
1644
|
+
|
|
1645
|
+
$create = new FleetOpsProcessMaintenanceTriggersHandleFake(['sandbox' => false, 'dry-run' => false]);
|
|
1646
|
+
$create->testSchedules = collect([$due, $notDue]);
|
|
1647
|
+
$create->workOrderCount = 6;
|
|
1648
|
+
$create->handle();
|
|
1649
|
+
|
|
1650
|
+
$skipOpen = new FleetOpsProcessMaintenanceTriggersHandleFake(['sandbox' => false, 'dry-run' => false]);
|
|
1651
|
+
$skipOpen->testSchedules = collect([$due]);
|
|
1652
|
+
$skipOpen->existingOpen = true;
|
|
1653
|
+
$skipOpen->handle();
|
|
1654
|
+
|
|
1655
|
+
expect($dryRun->operations)->toBe([['schedules', 'sandbox']])
|
|
1656
|
+
->and($dryRun->createdWorkOrders)->toBe([])
|
|
1657
|
+
->and($dryRun->dispatchedEvents)->toBe([])
|
|
1658
|
+
->and($dryRun->messages)->toContain(
|
|
1659
|
+
['info', 'Processing maintenance schedule triggers [DRY RUN] at 2026-02-03 04:05:06'],
|
|
1660
|
+
['info', 'Processed 1 schedule trigger(s) (dry run — no work orders created)']
|
|
1661
|
+
)
|
|
1662
|
+
->and($dryRun->messages[1][1])->toContain('Triggered: schedule schedule_public (Brake Service)')
|
|
1663
|
+
->and($create->operations)->toBe([
|
|
1664
|
+
['schedules', 'mysql'],
|
|
1665
|
+
['openWorkOrderExists', 'mysql', 'schedule-due'],
|
|
1666
|
+
['workOrderCount', 'mysql'],
|
|
1667
|
+
['createWorkOrder', 'mysql'],
|
|
1668
|
+
])
|
|
1669
|
+
->and($create->createdWorkOrders)->toHaveCount(1)
|
|
1670
|
+
->and($create->createdWorkOrders[0])->toMatchArray([
|
|
1671
|
+
'company_uuid' => 'company-uuid',
|
|
1672
|
+
'schedule_uuid' => 'schedule-due',
|
|
1673
|
+
'subject' => 'Brake Service',
|
|
1674
|
+
'category' => 'preventive_maintenance',
|
|
1675
|
+
'code' => 'WO-20260203-0007',
|
|
1676
|
+
'status' => 'open',
|
|
1677
|
+
'priority' => 'normal',
|
|
1678
|
+
'target_type' => Vehicle::class,
|
|
1679
|
+
'target_uuid' => 'vehicle-uuid',
|
|
1680
|
+
'assignee_type' => 'user',
|
|
1681
|
+
'assignee_uuid' => 'assignee-uuid',
|
|
1682
|
+
'instructions' => 'Inspect brakes and tires.',
|
|
1683
|
+
'created_by_uuid' => null,
|
|
1684
|
+
])
|
|
1685
|
+
->and($create->createdWorkOrders[0]['due_at']->toDateString())->toBe('2026-02-01')
|
|
1686
|
+
->and($create->createdWorkOrders[0]['opened_at']->toDateTimeString())->toBe('2026-02-03 04:05:06')
|
|
1687
|
+
->and($create->dispatchedEvents)->toBe([['schedule-due', 'work_order_created']])
|
|
1688
|
+
->and($create->messages)->toContain(
|
|
1689
|
+
['line', ' → Created work order work_order_created'],
|
|
1690
|
+
['info', 'Processed 1 schedule trigger(s).']
|
|
1691
|
+
)
|
|
1692
|
+
->and($skipOpen->operations)->toBe([
|
|
1693
|
+
['schedules', 'mysql'],
|
|
1694
|
+
['openWorkOrderExists', 'mysql', 'schedule-due'],
|
|
1695
|
+
])
|
|
1696
|
+
->and($skipOpen->createdWorkOrders)->toBe([])
|
|
1697
|
+
->and($skipOpen->dispatchedEvents)->toBe([])
|
|
1698
|
+
->and($skipOpen->messages)->toContain(
|
|
1699
|
+
['line', ' → Skipped: open work order already exists for this schedule.'],
|
|
1700
|
+
['info', 'Processed 0 schedule trigger(s).']
|
|
1701
|
+
);
|
|
1702
|
+
|
|
1703
|
+
Carbon::setTestNow();
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
test('track order distance command exits when another estimation run is locked', function () {
|
|
1707
|
+
$lock = new FleetOpsCommandLockFake(false);
|
|
1708
|
+
Cache::swap(new FleetOpsCommandCacheFake($lock));
|
|
1709
|
+
|
|
1710
|
+
$command = new FleetOpsTrackOrderDistanceAndTimeProbe([
|
|
1711
|
+
'provider' => null,
|
|
1712
|
+
'days' => 2,
|
|
1713
|
+
'chunk' => 250,
|
|
1714
|
+
'dry' => false,
|
|
1715
|
+
'no-lock' => false,
|
|
1716
|
+
]);
|
|
1717
|
+
|
|
1718
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
1719
|
+
->and($command->messages)->toContain(['info', 'Using provider: '])
|
|
1720
|
+
->and($command->messages)->toContain(['info', 'Looking back: last 2 day(s)'])
|
|
1721
|
+
->and($command->messages)->toContain(['info', 'Chunk size: 250'])
|
|
1722
|
+
->and($command->messages)->toContain(['warn', 'Another run appears to be in progress (lock active). Use --no-lock to bypass.'])
|
|
1723
|
+
->and($lock->released)->toBeFalse();
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
test('track order distance command processes chunks and records handled order errors', function () {
|
|
1727
|
+
Carbon::setTestNow(Carbon::parse('2026-07-26 12:00:00'));
|
|
1728
|
+
|
|
1729
|
+
$first = new FleetOpsTrackOrderFake(101);
|
|
1730
|
+
$second = new FleetOpsTrackOrderFake(102, shouldFail: true);
|
|
1731
|
+
$third = new FleetOpsTrackOrderFake(103);
|
|
1732
|
+
|
|
1733
|
+
$command = new FleetOpsTrackOrderDistanceAndTimeProbe([
|
|
1734
|
+
'provider' => 'osrm',
|
|
1735
|
+
'days' => 0,
|
|
1736
|
+
'chunk' => 10,
|
|
1737
|
+
'dry' => false,
|
|
1738
|
+
'no-lock' => true,
|
|
1739
|
+
], [$first, $second, $third]);
|
|
1740
|
+
|
|
1741
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
1742
|
+
->and($command->messages)->toContain(['info', 'Using provider: osrm'])
|
|
1743
|
+
->and($command->messages)->toContain(['info', 'Looking back: last 1 day(s)'])
|
|
1744
|
+
->and($command->messages)->toContain(['info', 'Chunk size: 50'])
|
|
1745
|
+
->and($command->messages)->toContain(['activeOrdersQuery', '2026-07-25 12:00:00'])
|
|
1746
|
+
->and($command->messages)->toContain(['createProgressBar', 3])
|
|
1747
|
+
->and($command->messages)->toContain(['error', 'Order 102 failed: distance provider failed'])
|
|
1748
|
+
->and($command->messages)->toContain(['newLine', 2])
|
|
1749
|
+
->and($command->messages)->toContain(['info', 'Updated 2/3 orders.'])
|
|
1750
|
+
->and($command->messages)->toContain(['warn', 'Encountered 1 error(s). Check logs for details.'])
|
|
1751
|
+
->and($command->query->calls)->toContain(['orderBy', 'id'])
|
|
1752
|
+
->and($command->query->calls)->toContain(['chunkById', 50])
|
|
1753
|
+
->and($command->progressBar->started)->toBe(1)
|
|
1754
|
+
->and($command->progressBar->advanced)->toBe(3)
|
|
1755
|
+
->and($command->progressBar->finished)->toBe(1)
|
|
1756
|
+
->and($first->distanceCalls)->toBe([['provider' => 'osrm']])
|
|
1757
|
+
->and($second->distanceCalls)->toBe([])
|
|
1758
|
+
->and($third->distanceCalls)->toBe([['provider' => 'osrm']]);
|
|
1759
|
+
|
|
1760
|
+
Carbon::setTestNow();
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
test('audit customer user conflicts outputs suspicious rows as json and applies company filters', function () {
|
|
1764
|
+
$adminUser = (object) [
|
|
1765
|
+
'uuid' => 'user-uuid',
|
|
1766
|
+
'email' => 'admin@example.test',
|
|
1767
|
+
'type' => 'admin',
|
|
1768
|
+
'companyUsers' => collect([
|
|
1769
|
+
(object) [
|
|
1770
|
+
'company_uuid' => 'company-uuid',
|
|
1771
|
+
'roles' => collect([
|
|
1772
|
+
(object) ['name' => 'Admin'],
|
|
1773
|
+
(object) ['name' => 'Fleet-Ops Customer'],
|
|
1774
|
+
]),
|
|
1775
|
+
],
|
|
1776
|
+
]),
|
|
1777
|
+
];
|
|
1778
|
+
$contact = fleetOpsAuditContact([], $adminUser);
|
|
1779
|
+
|
|
1780
|
+
$command = new FleetOpsAuditCustomerUserConflictsProbe([
|
|
1781
|
+
'company' => 'company-uuid',
|
|
1782
|
+
'json' => true,
|
|
1783
|
+
], [$contact]);
|
|
1784
|
+
|
|
1785
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
1786
|
+
->and($command->query->calls)->toContain(['where', 'company_uuid', 'company-uuid'])
|
|
1787
|
+
->and($command->messages)->toHaveCount(1)
|
|
1788
|
+
->and(json_decode($command->messages[0][1], true))->toMatchArray([
|
|
1789
|
+
[
|
|
1790
|
+
'contact_id' => 'contact_public',
|
|
1791
|
+
'contact_uuid' => 'contact-uuid',
|
|
1792
|
+
'contact_name' => 'Jane Customer',
|
|
1793
|
+
'contact_email' => 'jane@example.test',
|
|
1794
|
+
'user_uuid' => 'user-uuid',
|
|
1795
|
+
'user_email' => 'admin@example.test',
|
|
1796
|
+
'user_type' => 'admin',
|
|
1797
|
+
'roles' => 'Admin, Fleet-Ops Customer',
|
|
1798
|
+
'company_uuid' => 'company-uuid',
|
|
1799
|
+
'company_name' => 'Acme Logistics',
|
|
1800
|
+
'updated_at' => '2026-07-26 12:00:00',
|
|
1801
|
+
'reason' => 'linked user type is admin; linked user has non-customer roles: Admin',
|
|
1802
|
+
],
|
|
1803
|
+
]);
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
test('audit customer user conflicts handles missing users tables and clean audits', function () {
|
|
1807
|
+
$missingUserContact = fleetOpsAuditContact([
|
|
1808
|
+
'uuid' => 'missing-contact-uuid',
|
|
1809
|
+
'public_id' => 'missing_contact',
|
|
1810
|
+
'name' => 'Missing User',
|
|
1811
|
+
], null);
|
|
1812
|
+
$customerUser = (object) [
|
|
1813
|
+
'uuid' => 'customer-user-uuid',
|
|
1814
|
+
'email' => 'customer@example.test',
|
|
1815
|
+
'type' => 'customer',
|
|
1816
|
+
'companyUsers' => collect([
|
|
1817
|
+
(object) [
|
|
1818
|
+
'company_uuid' => 'company-uuid',
|
|
1819
|
+
'roles' => collect([
|
|
1820
|
+
(object) ['name' => 'Fleet-Ops Customer'],
|
|
1821
|
+
]),
|
|
1822
|
+
],
|
|
1823
|
+
]),
|
|
1824
|
+
];
|
|
1825
|
+
$cleanContact = fleetOpsAuditContact([
|
|
1826
|
+
'uuid' => 'clean-contact-uuid',
|
|
1827
|
+
'public_id' => 'clean_contact',
|
|
1828
|
+
], $customerUser);
|
|
1829
|
+
|
|
1830
|
+
$tableCommand = new FleetOpsAuditCustomerUserConflictsProbe([
|
|
1831
|
+
'company' => null,
|
|
1832
|
+
'json' => false,
|
|
1833
|
+
], [$missingUserContact, $cleanContact]);
|
|
1834
|
+
$cleanCommand = new FleetOpsAuditCustomerUserConflictsProbe([
|
|
1835
|
+
'company' => null,
|
|
1836
|
+
'json' => false,
|
|
1837
|
+
], [$cleanContact]);
|
|
1838
|
+
|
|
1839
|
+
expect($tableCommand->handle())->toBe(Command::SUCCESS)
|
|
1840
|
+
->and($tableCommand->tables)->toHaveCount(1)
|
|
1841
|
+
->and($tableCommand->tables[0][1][0])->toMatchArray([
|
|
1842
|
+
'contact_id' => 'missing_contact',
|
|
1843
|
+
'user_uuid' => null,
|
|
1844
|
+
'user_type' => null,
|
|
1845
|
+
'roles' => '',
|
|
1846
|
+
'reason' => 'missing linked user',
|
|
1847
|
+
])
|
|
1848
|
+
->and($cleanCommand->handle())->toBe(Command::SUCCESS)
|
|
1849
|
+
->and($cleanCommand->messages)->toContain(['info', 'No suspicious customer user conflicts found.']);
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
test('send maintenance reminders sends eligible reminders and skips ineligible schedules', function () {
|
|
1853
|
+
Carbon::setTestNow(Carbon::parse('2026-07-26 09:30:00'));
|
|
1854
|
+
|
|
1855
|
+
$eligible = fleetOpsReminderSchedule(
|
|
1856
|
+
'schedule-eligible',
|
|
1857
|
+
'schedule_public',
|
|
1858
|
+
'Quarterly inspection',
|
|
1859
|
+
'2026-07-28',
|
|
1860
|
+
[7, 1],
|
|
1861
|
+
'mechanic@example.test',
|
|
1862
|
+
);
|
|
1863
|
+
$alreadySent = fleetOpsReminderSchedule(
|
|
1864
|
+
'schedule-already',
|
|
1865
|
+
'schedule_already',
|
|
1866
|
+
'Already sent',
|
|
1867
|
+
'2026-07-26',
|
|
1868
|
+
[0],
|
|
1869
|
+
'lead@example.test',
|
|
1870
|
+
);
|
|
1871
|
+
$withoutEmail = fleetOpsReminderSchedule(
|
|
1872
|
+
'schedule-no-email',
|
|
1873
|
+
'schedule_no_email',
|
|
1874
|
+
'No assignee email',
|
|
1875
|
+
'2026-07-27',
|
|
1876
|
+
[7],
|
|
1877
|
+
null,
|
|
1878
|
+
);
|
|
1879
|
+
|
|
1880
|
+
$alreadySentKey = implode('|', ['sandbox', 'schedule-already', 0, '2026-07-26']);
|
|
1881
|
+
$command = new FleetOpsSendMaintenanceRemindersCommandFake([
|
|
1882
|
+
'sandbox' => true,
|
|
1883
|
+
'dry-run' => false,
|
|
1884
|
+
], [$alreadySentKey]);
|
|
1885
|
+
$command->testSchedules = collect([$eligible, $alreadySent, $withoutEmail]);
|
|
1886
|
+
|
|
1887
|
+
$command->handle();
|
|
1888
|
+
|
|
1889
|
+
expect($command->messages)->toContain(['schedules', 'sandbox'])
|
|
1890
|
+
->and($command->sent)->toBe([
|
|
1891
|
+
['mechanic@example.test', 'schedule-eligible', 7],
|
|
1892
|
+
])
|
|
1893
|
+
->and($command->records)->toBe([
|
|
1894
|
+
['sandbox', 'schedule-eligible', 7, '2026-07-28'],
|
|
1895
|
+
])
|
|
1896
|
+
->and($command->messages)->toContain(['info', 'Sent 1 reminder(s).'])
|
|
1897
|
+
->and(collect($command->messages)->pluck(1)->filter()->contains(fn ($message) => str_contains($message, 'no email on default assignee')))->toBeTrue();
|
|
1898
|
+
|
|
1899
|
+
Carbon::setTestNow();
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
test('send maintenance reminders dry run counts reminders without sending or recording', function () {
|
|
1903
|
+
Carbon::setTestNow(Carbon::parse('2026-07-26 09:30:00'));
|
|
1904
|
+
|
|
1905
|
+
$command = new FleetOpsSendMaintenanceRemindersCommandFake([
|
|
1906
|
+
'sandbox' => false,
|
|
1907
|
+
'dry-run' => true,
|
|
1908
|
+
]);
|
|
1909
|
+
$command->testSchedules = collect([
|
|
1910
|
+
fleetOpsReminderSchedule(
|
|
1911
|
+
'schedule-dry-run',
|
|
1912
|
+
'schedule_dry_run',
|
|
1913
|
+
'Dry run service',
|
|
1914
|
+
'2026-07-30',
|
|
1915
|
+
[7],
|
|
1916
|
+
'dry@example.test',
|
|
1917
|
+
),
|
|
1918
|
+
]);
|
|
1919
|
+
|
|
1920
|
+
$command->handle();
|
|
1921
|
+
|
|
1922
|
+
expect($command->messages)->toContain(['schedules', 'mysql'])
|
|
1923
|
+
->and($command->sent)->toBe([])
|
|
1924
|
+
->and($command->records)->toBe([])
|
|
1925
|
+
->and($command->messages)->toContain(['info', 'Sent 1 reminder(s) (dry run — no emails sent)']);
|
|
1926
|
+
|
|
1927
|
+
Carbon::setTestNow();
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
test('simulate order route navigation dispatches waypoint events with headings', function () {
|
|
1931
|
+
$driver = fleetOpsSimulationDriver();
|
|
1932
|
+
$command = new FleetOpsSimulateOrderRouteNavigationCommandFake([
|
|
1933
|
+
'order' => 'order_public',
|
|
1934
|
+
'driver' => 'driver_public',
|
|
1935
|
+
]);
|
|
1936
|
+
$command->order = fleetOpsSimulationOrder();
|
|
1937
|
+
$command->driver = $driver;
|
|
1938
|
+
$command->route = ['code' => 'Ok', 'routes' => [['geometry' => 'encoded-route']]];
|
|
1939
|
+
$command->waypoints = [
|
|
1940
|
+
new Point(1.30, 103.80),
|
|
1941
|
+
new Point(1.31, 103.81),
|
|
1942
|
+
new Point(1.32, 103.82),
|
|
1943
|
+
];
|
|
1944
|
+
|
|
1945
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
1946
|
+
->and($command->messages)->toContain(['findOrder', 'order_public'])
|
|
1947
|
+
->and($command->messages)->toContain(['findDriver', 'driver_public'])
|
|
1948
|
+
->and($command->messages)->toContain(['decodePolyline', 'encoded-route'])
|
|
1949
|
+
->and($command->messages)->toContain(['info', 'Order pickup point located at: 1 Pickup Street'])
|
|
1950
|
+
->and($command->messages)->toContain(['info', 'Order dropoff point located at: 99 Dropoff Avenue'])
|
|
1951
|
+
->and($command->events)->toHaveCount(3)
|
|
1952
|
+
->and($command->events[0][0])->toBe('driver_public')
|
|
1953
|
+
->and($command->events[0][2])->toHaveKey('heading')
|
|
1954
|
+
->and($command->events[0][2]['index'])->toBe(0)
|
|
1955
|
+
->and($command->events[1][2])->toHaveKey('heading')
|
|
1956
|
+
->and($command->events[2][2])->toBe(['index' => 2])
|
|
1957
|
+
->and($command->pauses)->toBe(3)
|
|
1958
|
+
->and($command->promptQuestions())->toBe([
|
|
1959
|
+
'order' => 'Which order ID should be used to simulate driving the route for?',
|
|
1960
|
+
]);
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
test('simulate order route navigation handles missing order driver fallback and unroutable responses', function () {
|
|
1964
|
+
$missingOrder = new FleetOpsSimulateOrderRouteNavigationCommandFake([
|
|
1965
|
+
'order' => 'missing-order',
|
|
1966
|
+
'driver' => null,
|
|
1967
|
+
]);
|
|
1968
|
+
|
|
1969
|
+
expect($missingOrder->handle())->toBe(Command::FAILURE)
|
|
1970
|
+
->and($missingOrder->messages)->toContain(['error', 'Order not found to simulate driving for.']);
|
|
1971
|
+
|
|
1972
|
+
$fallbackDriver = fleetOpsSimulationDriver('assigned-driver-uuid', 'assigned_driver', 'Assigned Driver');
|
|
1973
|
+
$fallback = new FleetOpsSimulateOrderRouteNavigationCommandFake([
|
|
1974
|
+
'order' => 'order_public',
|
|
1975
|
+
'driver' => 'missing-driver',
|
|
1976
|
+
]);
|
|
1977
|
+
$fallback->order = fleetOpsSimulationOrder($fallbackDriver);
|
|
1978
|
+
$fallback->route = ['code' => 'NoRoute'];
|
|
1979
|
+
$fallback->driver = null;
|
|
1980
|
+
|
|
1981
|
+
expect($fallback->handle())->toBe(Command::SUCCESS)
|
|
1982
|
+
->and($fallback->messages)->toContain(['findDriver', 'missing-driver'])
|
|
1983
|
+
->and($fallback->messages)->toContain(['error', 'The driver specified was not found, defaulting to driver assigned to order.'])
|
|
1984
|
+
->and($fallback->messages)->toContain(['info', 'Route navigation simulation completed.'])
|
|
1985
|
+
->and($fallback->events)->toBe([])
|
|
1986
|
+
->and($fallback->pauses)->toBe(0);
|
|
1987
|
+
|
|
1988
|
+
$withoutDriver = new FleetOpsSimulateOrderRouteNavigationCommandFake([
|
|
1989
|
+
'order' => 'order_public',
|
|
1990
|
+
'driver' => null,
|
|
1991
|
+
]);
|
|
1992
|
+
$withoutDriver->order = fleetOpsSimulationOrder();
|
|
1993
|
+
|
|
1994
|
+
expect($withoutDriver->handle())->toBe(Command::FAILURE)
|
|
1995
|
+
->and($withoutDriver->messages)->toContain(['error', 'No driver found to simulate the order.']);
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
test('dispatch adhoc command exits when no orders are dispatchable', function () {
|
|
1999
|
+
$command = new FleetOpsDispatchAdhocOrdersCommandFake([
|
|
2000
|
+
'sandbox' => false,
|
|
2001
|
+
'testing' => false,
|
|
2002
|
+
'days' => 0,
|
|
2003
|
+
]);
|
|
2004
|
+
|
|
2005
|
+
$command->handle();
|
|
2006
|
+
|
|
2007
|
+
expect($command->messages)->toContain(['info', 'Running in production mode.'])
|
|
2008
|
+
->and($command->messages)->toContain(['info', 'Looking back 1 day(s) for dispatchable orders...'])
|
|
2009
|
+
->and($command->messages)->toContain(['getDispatchableOrders', 1, 4, 72])
|
|
2010
|
+
->and($command->messages)->toContain(['info', 'No dispatchable orders found in the given timeframe.']);
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
test('dispatch adhoc command handles invalid pickups empty drivers and successful pings', function () {
|
|
2014
|
+
Carbon::setTestNow(Carbon::parse('2026-05-06 07:08:09'));
|
|
2015
|
+
|
|
2016
|
+
$invalidOrder = new FleetOpsDispatchAdhocOrdersOrderFake();
|
|
2017
|
+
$invalidOrder->public_id = 'order_invalid';
|
|
2018
|
+
$invalidOrder->dispatched_at = '2026-05-06 06:00:00';
|
|
2019
|
+
$invalidOrder->pickupLocation = null;
|
|
2020
|
+
|
|
2021
|
+
$emptyDriverOrder = new FleetOpsDispatchAdhocOrdersOrderFake();
|
|
2022
|
+
$emptyDriverOrder->public_id = 'order_empty';
|
|
2023
|
+
$emptyDriverOrder->dispatched_at = '2026-05-06 06:05:00';
|
|
2024
|
+
$emptyDriverOrder->pickupLocation = new Point(1.3, 103.8);
|
|
2025
|
+
$emptyDriverOrder->adhocDistance = 1200;
|
|
2026
|
+
|
|
2027
|
+
$pingOrder = new FleetOpsDispatchAdhocOrdersOrderFake();
|
|
2028
|
+
$pingOrder->public_id = 'order_ping';
|
|
2029
|
+
$pingOrder->dispatched_at = '2026-05-06 06:10:00';
|
|
2030
|
+
$pingOrder->pickupLocation = new Point(1.31, 103.81);
|
|
2031
|
+
$pingOrder->adhocDistance = 2400;
|
|
2032
|
+
|
|
2033
|
+
$driver = new FleetOpsDispatchAdhocOrdersDriverFake();
|
|
2034
|
+
$driver->public_id = 'driver_public';
|
|
2035
|
+
$driver->name = 'Jane Driver';
|
|
2036
|
+
|
|
2037
|
+
$command = new class(['sandbox' => true, 'testing' => true, 'days' => 3], $pingOrder, $driver) extends FleetOpsDispatchAdhocOrdersCommandFake {
|
|
2038
|
+
public function __construct(array $options, private Order $pingOrder, private Driver $driver)
|
|
2039
|
+
{
|
|
2040
|
+
parent::__construct($options);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
public function getNearbyDriversForOrder(Order $order, Point $pickup, int $distance, bool $testing = false): EloquentCollection
|
|
2044
|
+
{
|
|
2045
|
+
parent::getNearbyDriversForOrder($order, $pickup, $distance, $testing);
|
|
2046
|
+
|
|
2047
|
+
return $order === $this->pingOrder ? new EloquentCollection([$this->driver]) : new EloquentCollection();
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
$command->orders = new EloquentCollection([$invalidOrder, $emptyDriverOrder, $pingOrder]);
|
|
2051
|
+
|
|
2052
|
+
$command->handle();
|
|
2053
|
+
|
|
2054
|
+
expect($command->messages)->toContain(['info', 'Running in sandbox mode.'])
|
|
2055
|
+
->and($command->messages)->toContain(['alert', '3 orders found for ad-hoc dispatch. Current Time: 2026-05-06 07:08:09'])
|
|
2056
|
+
->and($command->messages)->toContain(['error', 'Invalid pickup location for order order_invalid'])
|
|
2057
|
+
->and($command->messages)->toContain(['warn', 'No available drivers found for order order_empty'])
|
|
2058
|
+
->and($command->messages)->toContain(['line', 'Checking order order_ping for nearby drivers within 2400 meters.'])
|
|
2059
|
+
->and($command->messages)->toContain(['info', 'Order order_ping dispatched successfully to 1 nearby drivers.'])
|
|
2060
|
+
->and($command->messages)->toContain(['info', 'Pinging driver Jane Driver (driver_public) ...'])
|
|
2061
|
+
->and($pingOrder->dispatchedForPing)->toBeTrue()
|
|
2062
|
+
->and($driver->notifications)->toBe([OrderPing::class])
|
|
2063
|
+
->and($command->tables)->toHaveCount(1);
|
|
2064
|
+
|
|
2065
|
+
Carbon::setTestNow();
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
test('dispatch adhoc command builds dispatchable order and nearby driver queries', function () {
|
|
2069
|
+
Carbon::setTestNow(Carbon::parse('2026-05-06 07:08:09'));
|
|
2070
|
+
|
|
2071
|
+
$command = new FleetOpsDispatchAdhocOrdersQueryProbe([
|
|
2072
|
+
'sandbox' => true,
|
|
2073
|
+
]);
|
|
2074
|
+
|
|
2075
|
+
expect($command->getDispatchableOrders(3))->toBe($command->orderQuery->results)
|
|
2076
|
+
->and($command->orderQuery->calls[0])->toBe(['connection', 'sandbox'])
|
|
2077
|
+
->and($command->orderQuery->calls)->toContain(['where', [['adhoc' => 1, 'dispatched' => 1, 'started' => 0]]])
|
|
2078
|
+
->and($command->orderQuery->calls)->toContain(['whereNull', 'driver_assigned_uuid'])
|
|
2079
|
+
->and($command->orderQuery->calls)->toContain(['whereNull', 'deleted_at'])
|
|
2080
|
+
->and($command->orderQuery->calls)->toContain(['where', ['status', '!=', 'canceled']])
|
|
2081
|
+
->and($command->orderQuery->calls)->toContain(['whereHas', 'company'])
|
|
2082
|
+
->and($command->orderQuery->calls)->toContain(['whereHas', 'payload'])
|
|
2083
|
+
->and($command->orderQuery->calls)->toContain(['with', ['company', 'payload']]);
|
|
2084
|
+
|
|
2085
|
+
$order = new Order();
|
|
2086
|
+
$order->setRawAttributes(['company_uuid' => 'company-uuid']);
|
|
2087
|
+
$point = new Point(1.3, 103.8);
|
|
2088
|
+
|
|
2089
|
+
expect($command->getNearbyDriversForOrder($order, $point, 500, true))->toBe($command->driverQuery->results)
|
|
2090
|
+
->and($command->driverQuery->calls)->toContain(['where', [['online' => 1]]])
|
|
2091
|
+
->and($command->driverQuery->calls)->toContain(['whereNull', 'deleted_at'])
|
|
2092
|
+
->and($command->driverQuery->calls)->not->toContain(['whereNotNull', 'location']);
|
|
2093
|
+
|
|
2094
|
+
$command = new FleetOpsDispatchAdhocOrdersQueryProbe([
|
|
2095
|
+
'sandbox' => false,
|
|
2096
|
+
]);
|
|
2097
|
+
|
|
2098
|
+
$command->getNearbyDriversForOrder($order, $point, 750, false);
|
|
2099
|
+
|
|
2100
|
+
expect($command->driverQuery->calls)->toContain(['whereNotNull', 'location'])
|
|
2101
|
+
->and($command->driverQuery->calls)->toContain(['distanceSphere', 'location', $point, 750])
|
|
2102
|
+
->and($command->driverQuery->calls)->toContain(['distanceSphereValue', 'location', $point]);
|
|
2103
|
+
|
|
2104
|
+
Carbon::setTestNow();
|
|
2105
|
+
});
|
|
2106
|
+
|
|
2107
|
+
test('legacy order config command creates configs and updates legacy orders', function () {
|
|
2108
|
+
$company = new Company();
|
|
2109
|
+
$company->setRawAttributes(['uuid' => 'company-uuid'], true);
|
|
2110
|
+
|
|
2111
|
+
$order = new FleetOpsLegacyOrderFake();
|
|
2112
|
+
$order->setRawAttributes(['uuid' => 'order-uuid', 'company_uuid' => 'company-uuid'], true);
|
|
2113
|
+
|
|
2114
|
+
$config = new OrderConfig();
|
|
2115
|
+
$config->setRawAttributes(['uuid' => 'config-uuid'], true);
|
|
2116
|
+
|
|
2117
|
+
$command = new FleetOpsFixLegacyOrderConfigsCommandFake(true);
|
|
2118
|
+
$command->testCompanies = collect([$company]);
|
|
2119
|
+
$command->testOrders = collect([$order]);
|
|
2120
|
+
$command->configs = ['company-uuid' => $config];
|
|
2121
|
+
|
|
2122
|
+
$command->handle();
|
|
2123
|
+
|
|
2124
|
+
expect($command->created)->toBe(['company-uuid'])
|
|
2125
|
+
->and($order->updates)->toBe([['order_config_uuid' => 'config-uuid']])
|
|
2126
|
+
->and($command->messages)->toContain(['info', 'Initializing transport config for 1 companies.'])
|
|
2127
|
+
->and($command->messages)->toContain(['info', '1 orders found for updating.'])
|
|
2128
|
+
->and($command->messages)->toContain(['info', 'All orders have been processed.'])
|
|
2129
|
+
->and($command->progressBar->started)->toBe(2)
|
|
2130
|
+
->and($command->progressBar->advanced)->toBe(2)
|
|
2131
|
+
->and($command->progressBar->finished)->toBe(2);
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
test('legacy order config command reports order update errors and continues', function () {
|
|
2135
|
+
$order = new FleetOpsLegacyOrderFake();
|
|
2136
|
+
$order->setRawAttributes(['uuid' => 'order-error', 'company_uuid' => 'company-uuid'], true);
|
|
2137
|
+
$order->failUpdate = true;
|
|
2138
|
+
|
|
2139
|
+
$config = new OrderConfig();
|
|
2140
|
+
$config->setRawAttributes(['uuid' => 'config-uuid'], true);
|
|
2141
|
+
|
|
2142
|
+
$command = new FleetOpsFixLegacyOrderConfigsCommandFake(false);
|
|
2143
|
+
$command->testOrders = collect([$order]);
|
|
2144
|
+
$command->configs = ['company-uuid' => $config];
|
|
2145
|
+
|
|
2146
|
+
$command->handle();
|
|
2147
|
+
|
|
2148
|
+
expect($command->created)->toBe([])
|
|
2149
|
+
->and($command->messages)->toContain(['error', 'order update failed'])
|
|
2150
|
+
->and($command->messages)->toContain(['error', 'Order ID: order-error'])
|
|
2151
|
+
->and($command->messages)->toContain(['info', 'All orders have been processed.'])
|
|
2152
|
+
->and($command->progressBar->advanced)->toBe(0)
|
|
2153
|
+
->and($command->progressBar->finished)->toBe(1);
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
test('fix customer companies command creates or links users and assigns missing company records', function () {
|
|
2157
|
+
$createdUser = new FleetOpsUserCommandFake('created-user', 'Created User', 'created@example.test');
|
|
2158
|
+
|
|
2159
|
+
$customerWithoutUser = new FleetOpsCustomerCommandFake();
|
|
2160
|
+
$customerWithoutUser->setRawAttributes([
|
|
2161
|
+
'name' => 'Created Customer',
|
|
2162
|
+
'email' => 'created@example.test',
|
|
2163
|
+
'phone' => '100',
|
|
2164
|
+
'company_uuid' => 'company-created',
|
|
2165
|
+
], true);
|
|
2166
|
+
$customerWithoutUser->createdUser = $createdUser;
|
|
2167
|
+
|
|
2168
|
+
$existingUser = new FleetOpsUserCommandFake('existing-user', 'Existing User', 'existing@example.test');
|
|
2169
|
+
|
|
2170
|
+
$customerWithExistingEmail = new FleetOpsCustomerCommandFake();
|
|
2171
|
+
$customerWithExistingEmail->setRawAttributes([
|
|
2172
|
+
'name' => 'Existing Customer',
|
|
2173
|
+
'email' => 'existing@example.test',
|
|
2174
|
+
'phone' => '200',
|
|
2175
|
+
'company_uuid' => 'company-existing',
|
|
2176
|
+
], true);
|
|
2177
|
+
$customerWithExistingEmail->failCreate = true;
|
|
2178
|
+
|
|
2179
|
+
$createdCompany = new Company();
|
|
2180
|
+
$createdCompany->setRawAttributes(['uuid' => 'company-created', 'name' => 'Created Company'], true);
|
|
2181
|
+
|
|
2182
|
+
$existingCompany = new Company();
|
|
2183
|
+
$existingCompany->setRawAttributes(['uuid' => 'company-existing', 'name' => 'Existing Company'], true);
|
|
2184
|
+
|
|
2185
|
+
$command = new FleetOpsFixCustomerCompaniesCommandFake();
|
|
2186
|
+
$command->testCustomers = collect([$customerWithoutUser, $customerWithExistingEmail]);
|
|
2187
|
+
$command->users = ['existing@example.test' => $existingUser];
|
|
2188
|
+
$command->missing = [
|
|
2189
|
+
'created-user:company-created' => true,
|
|
2190
|
+
'existing-user:company-existing' => true,
|
|
2191
|
+
];
|
|
2192
|
+
$command->companies = [
|
|
2193
|
+
'company-created' => $createdCompany,
|
|
2194
|
+
'company-existing' => $existingCompany,
|
|
2195
|
+
];
|
|
2196
|
+
|
|
2197
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
2198
|
+
->and($command->messages)->toContain(['info', 'User created for customer (Created Customer - created@example.test)'])
|
|
2199
|
+
->and($command->messages)->toContain(['error', 'existing user'])
|
|
2200
|
+
->and($command->messages)->toContain(['error', 'Existing user: existing@example.test'])
|
|
2201
|
+
->and($command->messages)->toContain(['info', 'Update customer user to existing user of the same email address.'])
|
|
2202
|
+
->and($customerWithExistingEmail->quietUpdates)->toBe([['user_uuid' => 'existing-user']])
|
|
2203
|
+
->and($createdUser->synced)->toBe([
|
|
2204
|
+
['email', FleetOpsCustomerCommandFake::class],
|
|
2205
|
+
['phone', FleetOpsCustomerCommandFake::class],
|
|
2206
|
+
])
|
|
2207
|
+
->and($existingUser->synced)->toBe([
|
|
2208
|
+
['email', FleetOpsCustomerCommandFake::class],
|
|
2209
|
+
['phone', FleetOpsCustomerCommandFake::class],
|
|
2210
|
+
])
|
|
2211
|
+
->and($createdUser->assigned)->toBe([['company-created', 'Administrator']])
|
|
2212
|
+
->and($existingUser->assigned)->toBe([['company-existing', 'Administrator']]);
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
test('fix driver companies command syncs assigned users and missing company assignments', function () {
|
|
2216
|
+
$user = new FleetOpsUserCommandFake('driver-user', 'Driver User', 'driver@example.test');
|
|
2217
|
+
|
|
2218
|
+
$driver = new Driver();
|
|
2219
|
+
$driver->setRawAttributes([
|
|
2220
|
+
'email' => 'driver@example.test',
|
|
2221
|
+
'phone' => '300',
|
|
2222
|
+
'company_uuid' => 'driver-company',
|
|
2223
|
+
], true);
|
|
2224
|
+
$driver->setRelation('user', $user);
|
|
2225
|
+
|
|
2226
|
+
$company = new Company();
|
|
2227
|
+
$company->setRawAttributes(['uuid' => 'driver-company', 'name' => 'Driver Company'], true);
|
|
2228
|
+
|
|
2229
|
+
$command = new FleetOpsFixDriverCompaniesCommandFake();
|
|
2230
|
+
$command->testDrivers = collect([$driver]);
|
|
2231
|
+
$command->missing = ['driver-user:driver-company' => true];
|
|
2232
|
+
$command->companies = ['driver-company' => $company];
|
|
2233
|
+
|
|
2234
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
2235
|
+
->and($user->synced)->toBe([
|
|
2236
|
+
['email', Driver::class],
|
|
2237
|
+
['phone', Driver::class],
|
|
2238
|
+
])
|
|
2239
|
+
->and($user->assigned)->toBe([['driver-company', 'Administrator']])
|
|
2240
|
+
->and($command->messages)->toContain(['line', 'Found driver Driver User (driver@example.test) which doesnt have correct company assignment.'])
|
|
2241
|
+
->and($command->messages)->toContain(['line', 'Driver driver@example.test was assigned to company: Driver Company']);
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
test('purge unpurchased service quotes command commits deletes and rolls back failures', function () {
|
|
2245
|
+
Carbon::setTestNow(Carbon::parse('2026-05-06 12:00:00'));
|
|
2246
|
+
|
|
2247
|
+
$deleted = new FleetOpsPurgeServiceQuotesCommandFake();
|
|
2248
|
+
$deleted->deletedCount = 3;
|
|
2249
|
+
|
|
2250
|
+
expect($deleted->handle())->toBe(Command::SUCCESS)
|
|
2251
|
+
->and($deleted->events[0])->toBe('disable')
|
|
2252
|
+
->and($deleted->events[1])->toBe('begin')
|
|
2253
|
+
->and($deleted->events[2][0])->toBe('purge')
|
|
2254
|
+
->and($deleted->events[2][1]->toDateTimeString())->toBe('2026-05-04 12:00:00')
|
|
2255
|
+
->and($deleted->events)->toContain('commit')
|
|
2256
|
+
->and($deleted->events)->toContain('enable')
|
|
2257
|
+
->and($deleted->messages)->toContain(['info', 'Successfully deleted 3 unpurchased service quotes.']);
|
|
2258
|
+
|
|
2259
|
+
$empty = new FleetOpsPurgeServiceQuotesCommandFake();
|
|
2260
|
+
|
|
2261
|
+
expect($empty->handle())->toBe(Command::SUCCESS)
|
|
2262
|
+
->and($empty->messages)->toContain(['info', 'No unpurchased service quotes found for deletion.']);
|
|
2263
|
+
|
|
2264
|
+
$failed = new FleetOpsPurgeServiceQuotesCommandFake();
|
|
2265
|
+
$failed->failure = new RuntimeException('delete failed');
|
|
2266
|
+
|
|
2267
|
+
expect($failed->handle())->toBe(Command::FAILURE)
|
|
2268
|
+
->and($failed->events)->toContain('rollback')
|
|
2269
|
+
->and($failed->events)->toContain('enable')
|
|
2270
|
+
->and($failed->messages)->toContain(['error', 'Error deleting unpurchased service quotes: delete failed']);
|
|
2271
|
+
|
|
2272
|
+
Carbon::setTestNow();
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
test('send driver notification command handles lookup validation choices and ping notifications', function () {
|
|
2276
|
+
$missing = new FleetOpsSendDriverNotificationCommandFake([
|
|
2277
|
+
'id' => null,
|
|
2278
|
+
'event' => 'assigned',
|
|
2279
|
+
]);
|
|
2280
|
+
|
|
2281
|
+
expect($missing->handle())->toBe(1)
|
|
2282
|
+
->and($missing->questions)->toBe([['Enter the order ID to trigger the notification', null]])
|
|
2283
|
+
->and($missing->messages)->toContain(['findOrder', 'asked_order'])
|
|
2284
|
+
->and($missing->messages)->toContain(['error', 'Order not found!']);
|
|
2285
|
+
|
|
2286
|
+
$withoutDriver = new FleetOpsSendDriverNotificationCommandFake([
|
|
2287
|
+
'id' => 'order_public',
|
|
2288
|
+
'event' => 'assigned',
|
|
2289
|
+
]);
|
|
2290
|
+
$withoutDriver->order = new FleetOpsNotificationOrderCommandFake();
|
|
2291
|
+
$withoutDriver->order->driverAssignedForTest = null;
|
|
2292
|
+
|
|
2293
|
+
expect($withoutDriver->handle())->toBe(1)
|
|
2294
|
+
->and($withoutDriver->messages)->toContain(['error', 'Order does not have a driver assigned!']);
|
|
2295
|
+
|
|
2296
|
+
$driver = new FleetOpsNotificationDriverCommandFake();
|
|
2297
|
+
$driver->locationForTest = 'driver-location';
|
|
2298
|
+
|
|
2299
|
+
$order = new FleetOpsNotificationOrderCommandFake();
|
|
2300
|
+
$order->driverAssignedForTest = $driver;
|
|
2301
|
+
$order->payloadForTest = new FleetOpsNotificationPayloadCommandFake('pickup-location');
|
|
2302
|
+
|
|
2303
|
+
$ping = new FleetOpsSendDriverNotificationCommandFake([
|
|
2304
|
+
'id' => 'order_public',
|
|
2305
|
+
'event' => 'ping',
|
|
2306
|
+
]);
|
|
2307
|
+
$ping->order = $order;
|
|
2308
|
+
|
|
2309
|
+
expect($ping->handle())->toBe(0)
|
|
2310
|
+
->and($ping->messages)->toContain(['distance', 'driver-location', 'pickup-location'])
|
|
2311
|
+
->and($ping->messages)->toContain(['info', "Notification 'ping' has been triggered for order ID 'order_public'."])
|
|
2312
|
+
->and($ping->sent)->toBe([[$driver, OrderPing::class, 'order_public', 1234]]);
|
|
2313
|
+
|
|
2314
|
+
$choice = new FleetOpsSendDriverNotificationCommandFake([
|
|
2315
|
+
'id' => 'order_public',
|
|
2316
|
+
'event' => null,
|
|
2317
|
+
]);
|
|
2318
|
+
$choice->order = $order;
|
|
2319
|
+
|
|
2320
|
+
expect($choice->handle())->toBe(0)
|
|
2321
|
+
->and($choice->choices[0][0])->toBe('Select the event to trigger')
|
|
2322
|
+
->and($choice->choices[0][1])->toBe(['assigned', 'canceled', 'dispatched', 'ping'])
|
|
2323
|
+
->and($choice->sent)->toBe([[$driver, OrderAssigned::class, 'order_public', null]]);
|
|
2324
|
+
|
|
2325
|
+
$invalid = new FleetOpsSendDriverNotificationCommandFake([
|
|
2326
|
+
'id' => 'order_public',
|
|
2327
|
+
'event' => 'unknown',
|
|
2328
|
+
]);
|
|
2329
|
+
$invalid->order = $order;
|
|
2330
|
+
|
|
2331
|
+
expect($invalid->handle())->toBe(1)
|
|
2332
|
+
->and($invalid->messages)->toContain(['error', 'Invalid event selected!']);
|
|
2333
|
+
});
|
|
2334
|
+
|
|
2335
|
+
test('dispatch orders command dispatches ready scheduled orders and warns for unready ones', function () {
|
|
2336
|
+
Carbon::setTestNow(Carbon::parse('2026-05-06 07:08:09'));
|
|
2337
|
+
|
|
2338
|
+
$ready = new FleetOpsScheduledOrderCommandFake();
|
|
2339
|
+
$ready->publicIdForTest = 'order_ready';
|
|
2340
|
+
$ready->scheduledAtForTest = '2026-05-06 07:00:00';
|
|
2341
|
+
$ready->ready = true;
|
|
2342
|
+
|
|
2343
|
+
$waiting = new FleetOpsScheduledOrderCommandFake();
|
|
2344
|
+
$waiting->publicIdForTest = 'order_waiting';
|
|
2345
|
+
$waiting->scheduledAtForTest = '2026-05-06 08:00:00';
|
|
2346
|
+
|
|
2347
|
+
$command = new FleetOpsDispatchOrdersCommandFake(['sandbox' => 'true']);
|
|
2348
|
+
$command->orders = new EloquentCollection([$ready, $waiting]);
|
|
2349
|
+
|
|
2350
|
+
$command->handle();
|
|
2351
|
+
|
|
2352
|
+
expect($command->messages)->toContain(['info', 'Running in sandbox mode.'])
|
|
2353
|
+
->and($command->messages)->toContain(['getScheduledOrders', true])
|
|
2354
|
+
->and($command->messages)->toContain(['alert', 'Found 2 orders scheduled for dispatch. Current Time: 2026-05-06 07:08:09'])
|
|
2355
|
+
->and($command->messages)->toContain(['info', 'Order order_ready dispatched successfully (2026-05-06 07:00:00).'])
|
|
2356
|
+
->and($command->messages)->toContain(['warn', 'Order order_waiting is not ready for dispatch (2026-05-06 08:00:00).'])
|
|
2357
|
+
->and($ready->dispatchedForTest)->toBeTrue()
|
|
2358
|
+
->and($waiting->dispatchedForTest)->toBeFalse();
|
|
2359
|
+
|
|
2360
|
+
Carbon::setTestNow();
|
|
2361
|
+
});
|
|
2362
|
+
|
|
2363
|
+
test('assign driver roles command assigns non admin driver users and reports failures', function () {
|
|
2364
|
+
$company = new Company();
|
|
2365
|
+
$company->setRawAttributes(['uuid' => 'company-uuid', 'name' => 'Acme Logistics'], true);
|
|
2366
|
+
|
|
2367
|
+
$driverUser = new FleetOpsRoleUserCommandFake('driver@example.test');
|
|
2368
|
+
$driverUser->drivers = ['company-uuid' => (object) ['uuid' => 'driver-uuid']];
|
|
2369
|
+
|
|
2370
|
+
$adminUser = new FleetOpsRoleUserCommandFake('admin@example.test');
|
|
2371
|
+
$adminUser->admin = true;
|
|
2372
|
+
$adminUser->drivers = ['company-uuid' => (object) ['uuid' => 'admin-driver']];
|
|
2373
|
+
|
|
2374
|
+
$failingUser = new FleetOpsRoleUserCommandFake('fail@example.test');
|
|
2375
|
+
$failingUser->drivers = ['company-uuid' => (object) ['uuid' => 'fail-driver']];
|
|
2376
|
+
$failingUser->failure = new RuntimeException('role store failed');
|
|
2377
|
+
|
|
2378
|
+
$company->setRelation('users', collect([$driverUser, $adminUser, $failingUser, (object) ['email' => 'ignored@example.test']]));
|
|
2379
|
+
|
|
2380
|
+
$command = new FleetOpsAssignDriverRolesCommandFake();
|
|
2381
|
+
$command->testCompanies = collect([$company]);
|
|
2382
|
+
|
|
2383
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
2384
|
+
->and($driverUser->roles)->toBe(['Driver'])
|
|
2385
|
+
->and($adminUser->roles)->toBe([])
|
|
2386
|
+
->and($failingUser->roles)->toBe([])
|
|
2387
|
+
->and($driverUser->companies)->toBe(['company-uuid'])
|
|
2388
|
+
->and($command->messages)->toContain(['info', 'Acme Logistics - Driver: driver@example.test has been made Driver.'])
|
|
2389
|
+
->and($command->messages)->toContain(['error', 'role store failed']);
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
test('assign customer roles command creates missing users and reports assignment errors', function () {
|
|
2393
|
+
$createdUser = new FleetOpsRoleUserCommandFake('created@example.test');
|
|
2394
|
+
$existingUser = new FleetOpsRoleUserCommandFake('existing@example.test');
|
|
2395
|
+
$failingUser = new FleetOpsRoleUserCommandFake('fail@example.test');
|
|
2396
|
+
$failingUser->failure = new RuntimeException('customer role failed');
|
|
2397
|
+
|
|
2398
|
+
$needsUser = new FleetOpsCustomerRoleCommandFake();
|
|
2399
|
+
$needsUser->setRawAttributes(['name' => 'Created Customer', 'email' => 'created@example.test'], true);
|
|
2400
|
+
$needsUser->createdUser = $createdUser;
|
|
2401
|
+
|
|
2402
|
+
$existing = new FleetOpsCustomerRoleCommandFake();
|
|
2403
|
+
$existing->setRawAttributes(['name' => 'Existing Customer', 'email' => 'existing@example.test'], true);
|
|
2404
|
+
|
|
2405
|
+
$failing = new FleetOpsCustomerRoleCommandFake();
|
|
2406
|
+
$failing->setRawAttributes(['name' => 'Fail Customer', 'email' => 'fail@example.test'], true);
|
|
2407
|
+
|
|
2408
|
+
$command = new FleetOpsAssignCustomerRolesCommandFake();
|
|
2409
|
+
$command->testCustomers = collect([$needsUser, $existing, $failing]);
|
|
2410
|
+
$command->customerUsers = [
|
|
2411
|
+
spl_object_id($existing) => $existingUser,
|
|
2412
|
+
spl_object_id($failing) => $failingUser,
|
|
2413
|
+
];
|
|
2414
|
+
|
|
2415
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
2416
|
+
->and($createdUser->roles)->toBe(['Fleet-Ops Customer'])
|
|
2417
|
+
->and($existingUser->roles)->toBe(['Fleet-Ops Customer'])
|
|
2418
|
+
->and($failingUser->roles)->toBe([])
|
|
2419
|
+
->and($command->messages)->toContain(['info', 'Created Customer - Customer: created@example.test has been assigned the Customer role.'])
|
|
2420
|
+
->and($command->messages)->toContain(['info', 'Existing Customer - Customer: existing@example.test has been assigned the Customer role.'])
|
|
2421
|
+
->and($command->messages)->toContain(['error', 'customer role failed']);
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2424
|
+
test('test email command rejects unsupported email types before sending mail', function () {
|
|
2425
|
+
$command = new class extends TestEmail {
|
|
2426
|
+
public array $messages = [];
|
|
2427
|
+
|
|
2428
|
+
public function argument($key = null)
|
|
2429
|
+
{
|
|
2430
|
+
$arguments = ['email' => 'customer@example.test'];
|
|
2431
|
+
|
|
2432
|
+
return $key === null ? $arguments : ($arguments[$key] ?? null);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
public function option($key = null)
|
|
2436
|
+
{
|
|
2437
|
+
$options = ['type' => 'unknown'];
|
|
2438
|
+
|
|
2439
|
+
return $key === null ? $options : ($options[$key] ?? null);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
public function info($string, $verbosity = null)
|
|
2443
|
+
{
|
|
2444
|
+
$this->messages[] = ['info', $string];
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
public function error($string, $verbosity = null)
|
|
2448
|
+
{
|
|
2449
|
+
$this->messages[] = ['error', $string];
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2453
|
+
expect($command->handle())->toBe(Command::FAILURE)
|
|
2454
|
+
->and($command->messages)->toContain(['error', 'Unknown email type: unknown']);
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
test('test email command reports send failures as a command failure', function () {
|
|
2458
|
+
$command = new class extends TestEmail {
|
|
2459
|
+
public array $messages = [];
|
|
2460
|
+
|
|
2461
|
+
public function argument($key = null)
|
|
2462
|
+
{
|
|
2463
|
+
$arguments = ['email' => 'customer@example.test'];
|
|
2464
|
+
|
|
2465
|
+
return $key === null ? $arguments : ($arguments[$key] ?? null);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
public function option($key = null)
|
|
2469
|
+
{
|
|
2470
|
+
$options = ['type' => 'customer_credentials'];
|
|
2471
|
+
|
|
2472
|
+
return $key === null ? $options : ($options[$key] ?? null);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
public function info($string, $verbosity = null)
|
|
2476
|
+
{
|
|
2477
|
+
$this->messages[] = ['info', $string];
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
public function error($string, $verbosity = null)
|
|
2481
|
+
{
|
|
2482
|
+
$this->messages[] = ['error', $string];
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
|
|
2486
|
+
// The mail transport is unavailable in the harness, so the send throws and
|
|
2487
|
+
// the command reports the failure rather than surfacing the exception
|
|
2488
|
+
expect($command->handle())->toBe(Command::FAILURE)
|
|
2489
|
+
->and(collect($command->messages)->pluck(0))->toContain('error');
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
test('track order distance command exits early when no qualifying orders exist', function () {
|
|
2493
|
+
Carbon::setTestNow(Carbon::parse('2026-07-26 12:00:00'));
|
|
2494
|
+
|
|
2495
|
+
$command = new FleetOpsTrackOrderDistanceAndTimeProbe([
|
|
2496
|
+
'provider' => null,
|
|
2497
|
+
'days' => 2,
|
|
2498
|
+
'chunk' => 100,
|
|
2499
|
+
'dry' => false,
|
|
2500
|
+
'no-lock' => true,
|
|
2501
|
+
], []);
|
|
2502
|
+
|
|
2503
|
+
expect($command->handle())->toBe(Command::SUCCESS)
|
|
2504
|
+
->and($command->messages)->toContain(['info', 'No qualifying orders found. Exiting.']);
|
|
2505
|
+
|
|
2506
|
+
Carbon::setTestNow();
|
|
2507
|
+
});
|
|
2508
|
+
|
|
2509
|
+
test('track order distance real query and progress bar builders execute', function () {
|
|
2510
|
+
$connection = new Illuminate\Database\SQLiteConnection(new PDO('sqlite::memory:'));
|
|
2511
|
+
$resolver = new Illuminate\Database\ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
2512
|
+
$resolver->setDefaultConnection('mysql');
|
|
2513
|
+
Model::setConnectionResolver($resolver);
|
|
2514
|
+
$schema = $connection->getSchemaBuilder();
|
|
2515
|
+
foreach (['orders' => ['uuid', 'public_id', 'company_uuid', 'payload_uuid', 'status', 'started_at', '_key'], 'payloads' => ['uuid', 'public_id', 'company_uuid', '_key']] as $table => $columns) {
|
|
2516
|
+
$schema->create($table, function ($blueprint) use ($columns) {
|
|
2517
|
+
$blueprint->increments('id');
|
|
2518
|
+
foreach ($columns as $column) {
|
|
2519
|
+
$blueprint->string($column)->nullable();
|
|
2520
|
+
}
|
|
2521
|
+
$blueprint->timestamps();
|
|
2522
|
+
$blueprint->timestamp('deleted_at')->nullable();
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
$connection->table('payloads')->insert(['uuid' => 'payload-track-1', 'company_uuid' => 'company-1']);
|
|
2526
|
+
$connection->table('orders')->insert([
|
|
2527
|
+
['uuid' => 'order-track-active', 'company_uuid' => 'company-1', 'payload_uuid' => 'payload-track-1', 'status' => 'started', 'started_at' => '2026-07-25 10:00:00', 'created_at' => '2026-07-25 10:00:00', 'updated_at' => '2026-07-25 10:00:00'],
|
|
2528
|
+
['uuid' => 'order-track-done', 'company_uuid' => 'company-1', 'payload_uuid' => 'payload-track-1', 'status' => 'completed', 'started_at' => '2026-07-25 10:00:00', 'created_at' => '2026-07-25 10:00:00', 'updated_at' => '2026-07-25 10:00:00'],
|
|
2529
|
+
]);
|
|
2530
|
+
|
|
2531
|
+
$command = new TrackOrderDistanceAndTime();
|
|
2532
|
+
$reflection = new ReflectionMethod(TrackOrderDistanceAndTime::class, 'activeOrdersQuery');
|
|
2533
|
+
$reflection->setAccessible(true);
|
|
2534
|
+
$query = $reflection->invoke($command, Illuminate\Support\Carbon::parse('2026-07-20 00:00:00'));
|
|
2535
|
+
expect($query->count('id'))->toBe(1);
|
|
2536
|
+
|
|
2537
|
+
$output = new class {
|
|
2538
|
+
public array $bars = [];
|
|
2539
|
+
|
|
2540
|
+
public function createProgressBar(int $total)
|
|
2541
|
+
{
|
|
2542
|
+
$this->bars[] = $total;
|
|
2543
|
+
|
|
2544
|
+
return new FleetOpsTrackProgressBarFake();
|
|
2545
|
+
}
|
|
2546
|
+
};
|
|
2547
|
+
$outputProperty = new ReflectionProperty(Command::class, 'output');
|
|
2548
|
+
$outputProperty->setAccessible(true);
|
|
2549
|
+
$outputProperty->setValue($command, $output);
|
|
2550
|
+
|
|
2551
|
+
$barMethod = new ReflectionMethod(TrackOrderDistanceAndTime::class, 'createProgressBar');
|
|
2552
|
+
$barMethod->setAccessible(true);
|
|
2553
|
+
expect($barMethod->invoke($command, 7))->toBeInstanceOf(FleetOpsTrackProgressBarFake::class)
|
|
2554
|
+
->and($output->bars)->toBe([7]);
|
|
2555
|
+
});
|
|
2556
|
+
|
|
2557
|
+
class FleetOpsRecordedDriverNotification
|
|
2558
|
+
{
|
|
2559
|
+
public static array $constructed = [];
|
|
2560
|
+
|
|
2561
|
+
public function __construct(...$args)
|
|
2562
|
+
{
|
|
2563
|
+
static::$constructed[] = $args;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
class FleetOpsThrowingDriverNotificationCommandFake extends FleetOpsSendDriverNotificationCommandFake
|
|
2568
|
+
{
|
|
2569
|
+
protected function notifyDriver($driver, string $notificationClass, Order $order, mixed $distance = null): void
|
|
2570
|
+
{
|
|
2571
|
+
throw new Exception('notification channel offline');
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
test('send driver notification real helpers resolve orders distances and notify branches', function () {
|
|
2576
|
+
// Real order lookup against sqlite
|
|
2577
|
+
$connection = new Illuminate\Database\SQLiteConnection(new PDO('sqlite::memory:'));
|
|
2578
|
+
$resolver = new Illuminate\Database\ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
2579
|
+
$resolver->setDefaultConnection('mysql');
|
|
2580
|
+
Model::setConnectionResolver($resolver);
|
|
2581
|
+
$schema = $connection->getSchemaBuilder();
|
|
2582
|
+
$schema->create('orders', function ($blueprint) {
|
|
2583
|
+
$blueprint->increments('id');
|
|
2584
|
+
foreach (['uuid', 'public_id', 'company_uuid', 'status', 'meta', '_key'] as $column) {
|
|
2585
|
+
$blueprint->string($column)->nullable();
|
|
2586
|
+
}
|
|
2587
|
+
$blueprint->timestamps();
|
|
2588
|
+
$blueprint->timestamp('deleted_at')->nullable();
|
|
2589
|
+
});
|
|
2590
|
+
$connection->table('orders')->insert(['uuid' => 'order-sdn-1', 'public_id' => 'order_sdnreal1', 'company_uuid' => 'company-1', 'status' => 'created']);
|
|
2591
|
+
|
|
2592
|
+
$command = new SendDriverNotification();
|
|
2593
|
+
$helper = function (string $method, ...$arguments) use ($command) {
|
|
2594
|
+
$reflection = new ReflectionMethod(SendDriverNotification::class, $method);
|
|
2595
|
+
$reflection->setAccessible(true);
|
|
2596
|
+
|
|
2597
|
+
return $reflection->invoke($command, ...$arguments);
|
|
2598
|
+
};
|
|
2599
|
+
|
|
2600
|
+
expect($helper('findOrder', 'order_sdnreal1')?->uuid)->toBe('order-sdn-1')
|
|
2601
|
+
->and($helper('findOrder', 'order_missing0'))->toBeNull();
|
|
2602
|
+
|
|
2603
|
+
// Real driving matrix from two points
|
|
2604
|
+
$matrix = $helper('calculateDrivingDistanceAndTime', new Point(1.30, 103.80), new Point(1.31, 103.81));
|
|
2605
|
+
expect($matrix->distance)->toBeGreaterThan(0);
|
|
2606
|
+
|
|
2607
|
+
// Both notify branches construct the notification with and without distance
|
|
2608
|
+
$order = new Order();
|
|
2609
|
+
$order->setRawAttributes(['uuid' => 'order-sdn-1', 'public_id' => 'order_sdnreal1'], true);
|
|
2610
|
+
$driver = new class {
|
|
2611
|
+
public array $notified = [];
|
|
2612
|
+
|
|
2613
|
+
public function notify($notification)
|
|
2614
|
+
{
|
|
2615
|
+
$this->notified[] = $notification;
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
FleetOpsRecordedDriverNotification::$constructed = [];
|
|
2619
|
+
$helper('notifyDriver', $driver, FleetOpsRecordedDriverNotification::class, $order, 4321);
|
|
2620
|
+
$helper('notifyDriver', $driver, FleetOpsRecordedDriverNotification::class, $order);
|
|
2621
|
+
expect($driver->notified)->toHaveCount(2)
|
|
2622
|
+
->and(FleetOpsRecordedDriverNotification::$constructed[0])->toHaveCount(2)
|
|
2623
|
+
->and(FleetOpsRecordedDriverNotification::$constructed[1])->toHaveCount(1);
|
|
2624
|
+
|
|
2625
|
+
// Notification failures surface as command errors
|
|
2626
|
+
$driverFake = new FleetOpsNotificationDriverCommandFake();
|
|
2627
|
+
$orderFake = new FleetOpsNotificationOrderCommandFake();
|
|
2628
|
+
$orderFake->driverAssignedForTest = $driverFake;
|
|
2629
|
+
|
|
2630
|
+
$failing = new FleetOpsThrowingDriverNotificationCommandFake([
|
|
2631
|
+
'id' => 'order_public',
|
|
2632
|
+
'event' => 'assigned',
|
|
2633
|
+
]);
|
|
2634
|
+
$failing->order = $orderFake;
|
|
2635
|
+
|
|
2636
|
+
expect($failing->handle())->toBe(0)
|
|
2637
|
+
->and($failing->messages)->toContain(['error', 'notification channel offline']);
|
|
2638
|
+
});
|
|
2639
|
+
|
|
2640
|
+
if (!function_exists('Fleetbase\FleetOps\Console\Commands\event')) {
|
|
2641
|
+
eval('namespace Fleetbase\FleetOps\Console\Commands; function event($event = null) { $GLOBALS["fleetopsSimulatedEvents"][] = $event; return []; }');
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
test('simulate order route navigation real helpers resolve lookups routes and events', function () {
|
|
2645
|
+
$connection = new Illuminate\Database\SQLiteConnection(new PDO('sqlite::memory:'));
|
|
2646
|
+
$resolver = new Illuminate\Database\ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
2647
|
+
$resolver->setDefaultConnection('mysql');
|
|
2648
|
+
Model::setConnectionResolver($resolver);
|
|
2649
|
+
$schema = $connection->getSchemaBuilder();
|
|
2650
|
+
foreach (['orders' => ['uuid', 'public_id', 'company_uuid', 'status', '_key'], 'drivers' => ['uuid', 'public_id', 'company_uuid', 'user_uuid', '_key'], 'users' => ['uuid', 'public_id', 'company_uuid', '_key']] as $table => $columns) {
|
|
2651
|
+
$schema->create($table, function ($blueprint) use ($columns) {
|
|
2652
|
+
$blueprint->increments('id');
|
|
2653
|
+
foreach ($columns as $column) {
|
|
2654
|
+
$blueprint->string($column)->nullable();
|
|
2655
|
+
}
|
|
2656
|
+
$blueprint->timestamps();
|
|
2657
|
+
$blueprint->timestamp('deleted_at')->nullable();
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
$connection->table('orders')->insert(['uuid' => 'order-simnav-1', 'public_id' => 'order_simnav1', 'company_uuid' => 'company-1', 'status' => 'created']);
|
|
2661
|
+
$connection->table('users')->insert(['uuid' => 'user-simnav-1', 'company_uuid' => 'company-1']);
|
|
2662
|
+
$connection->table('drivers')->insert(['uuid' => 'driver-simnav-1', 'public_id' => 'driver_simnav1', 'company_uuid' => 'company-1', 'user_uuid' => 'user-simnav-1']);
|
|
2663
|
+
|
|
2664
|
+
$command = new SimulateOrderRouteNavigation();
|
|
2665
|
+
$helper = function (string $method, ...$arguments) use ($command) {
|
|
2666
|
+
$reflection = new ReflectionMethod(SimulateOrderRouteNavigation::class, $method);
|
|
2667
|
+
$reflection->setAccessible(true);
|
|
2668
|
+
|
|
2669
|
+
return $reflection->invoke($command, ...$arguments);
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
expect($helper('findOrder', 'order_simnav1')?->uuid)->toBe('order-simnav-1')
|
|
2673
|
+
->and($helper('findDriver', 'driver_simnav1')?->uuid)->toBe('driver-simnav-1');
|
|
2674
|
+
|
|
2675
|
+
// Route retrieval flows through the faked OSRM client and polyline decode
|
|
2676
|
+
Cache::swap(new Illuminate\Cache\Repository(new Illuminate\Cache\ArrayStore()));
|
|
2677
|
+
Illuminate\Support\Facades\Http::swap(new Illuminate\Http\Client\Factory());
|
|
2678
|
+
Illuminate\Support\Facades\Http::fake(fn () => Illuminate\Support\Facades\Http::response(['code' => 'Ok', 'routes' => [['geometry' => '_p~iF~ps|U_ulLnnqC_mqNvxq`@']]]));
|
|
2679
|
+
$route = $helper('getRoute', new Point(1.30, 103.80), new Point(1.31, 103.81));
|
|
2680
|
+
expect($route['code'])->toBe('Ok');
|
|
2681
|
+
|
|
2682
|
+
$points = $helper('decodePolyline', '_p~iF~ps|U_ulLnnqC_mqNvxq`@');
|
|
2683
|
+
expect($points)->toHaveCount(3);
|
|
2684
|
+
|
|
2685
|
+
// Location events dispatch through the namespaced event shim
|
|
2686
|
+
$GLOBALS['fleetopsSimulatedEvents'] = [];
|
|
2687
|
+
$driver = Driver::where('uuid', 'driver-simnav-1')->first();
|
|
2688
|
+
$helper('dispatchLocationChanged', $driver, $points[0], ['progress' => 10]);
|
|
2689
|
+
expect($GLOBALS['fleetopsSimulatedEvents'])->toHaveCount(1);
|
|
2690
|
+
|
|
2691
|
+
// The waypoint pacing pause simply sleeps between broadcasts
|
|
2692
|
+
$paused = microtime(true);
|
|
2693
|
+
$helper('pauseBetweenWaypoints');
|
|
2694
|
+
expect(microtime(true) - $paused)->toBeGreaterThan(2.5);
|
|
2695
|
+
});
|