@fleetbase/fleetops-engine 0.6.69 → 0.6.71
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/RELEASE.md +21 -14
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- package/addon/components/fuel-integration/hub.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +37 -17
- package/addon/components/map/leaflet-live-map.hbs +4 -242
- package/addon/components/map/leaflet-live-map.js +2 -1
- package/addon/components/map/marker-card/driver.hbs +57 -0
- package/addon/components/map/marker-card/driver.js +3 -0
- package/addon/components/map/marker-card/vehicle.hbs +78 -0
- package/addon/components/map/marker-card/vehicle.js +3 -0
- package/addon/components/order/details/activity.hbs +8 -6
- package/addon/components/order/details/activity.js +9 -0
- package/addon/components/order/details.hbs +29 -0
- package/addon/components/order/details.js +26 -0
- package/addon/components/order/form/details.hbs +30 -4
- package/addon/components/order/form/details.js +46 -0
- package/addon/components/order/form/route.hbs +4 -2
- package/addon/components/order/form/route.js +11 -0
- package/addon/components/order/form.hbs +12 -0
- package/addon/components/order/form.js +21 -1
- package/addon/components/order/kanban.js +72 -0
- package/addon/components/order-config-manager/activity-flow.js +45 -10
- package/addon/components/order-config-manager/lifecycle.hbs +250 -0
- package/addon/components/order-config-manager/lifecycle.js +358 -0
- package/addon/components/order-config-manager.js +5 -1
- package/addon/components/telematic/hub.hbs +1 -1
- package/addon/components/widget/live-fleet.hbs +8 -40
- package/addon/controllers/analytics/reports/index.js +15 -1
- package/addon/controllers/connectivity/devices/index/details.js +1 -0
- package/addon/controllers/connectivity/devices/index.js +12 -2
- package/addon/controllers/connectivity/events/index.js +31 -1
- package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
- package/addon/controllers/connectivity/sensors/index/details.js +1 -1
- package/addon/controllers/connectivity/sensors/index.js +41 -1
- package/addon/controllers/connectivity/telematics/index.js +4 -0
- package/addon/controllers/maintenance/equipment/index/details.js +2 -2
- package/addon/controllers/maintenance/equipment/index.js +36 -10
- package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
- package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
- package/addon/controllers/maintenance/inspection-submissions/index.js +56 -10
- package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
- package/addon/controllers/maintenance/maintenances/index.js +24 -1
- package/addon/controllers/maintenance/parts/index/details.js +2 -2
- package/addon/controllers/maintenance/parts/index.js +36 -9
- package/addon/controllers/maintenance/schedules/index.js +48 -6
- package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
- package/addon/controllers/maintenance/work-orders/index.js +47 -9
- package/addon/controllers/management/contacts/customers.js +13 -0
- package/addon/controllers/management/contacts/index.js +47 -1
- package/addon/controllers/management/drivers/index/details.js +1 -0
- package/addon/controllers/management/drivers/index.js +36 -2
- package/addon/controllers/management/fleets/index/details.js +1 -0
- package/addon/controllers/management/fleets/index.js +43 -1
- package/addon/controllers/management/fuel-reports/index.js +26 -2
- package/addon/controllers/management/fuel-transactions/index.js +45 -7
- package/addon/controllers/management/issues/index/details.js +1 -0
- package/addon/controllers/management/issues/index.js +26 -2
- package/addon/controllers/management/places/index.js +40 -1
- package/addon/controllers/management/trailers/index/details.js +1 -0
- package/addon/controllers/management/trailers/index.js +50 -2
- package/addon/controllers/management/vehicles/index/details.js +1 -0
- package/addon/controllers/management/vehicles/index.js +37 -2
- package/addon/controllers/management/vendors/index.js +30 -2
- package/addon/controllers/operations/orders/index/details.js +61 -5
- package/addon/controllers/operations/orders/index.js +66 -4
- package/addon/controllers/operations/service-rates/index.js +18 -1
- package/addon/controllers/settings/index.js +13 -0
- package/addon/controllers/settings/telematics.js +113 -0
- package/addon/extension.js +70 -6
- package/addon/routes/analytics/index.js +14 -1
- package/addon/routes/analytics/reports/index/details.js +1 -1
- package/addon/routes/analytics/reports/index/edit.js +1 -1
- package/addon/routes/analytics/reports/index/new.js +14 -1
- package/addon/routes/analytics/reports.js +14 -1
- package/addon/routes/connectivity/devices/index/new.js +14 -1
- package/addon/routes/connectivity/devices.js +14 -1
- package/addon/routes/connectivity/events.js +14 -1
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
- package/addon/routes/connectivity/fuel-providers/details.js +9 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
- package/addon/routes/connectivity/fuel-providers/new.js +14 -1
- package/addon/routes/connectivity/fuel-providers.js +14 -1
- package/addon/routes/connectivity/sensors/index/new.js +14 -1
- package/addon/routes/connectivity/sensors.js +14 -1
- package/addon/routes/connectivity/telematics/new.js +14 -1
- package/addon/routes/connectivity/telematics.js +14 -1
- package/addon/routes/connectivity/tracking.js +14 -1
- package/addon/routes/maintenance/equipment/index/details.js +2 -2
- package/addon/routes/maintenance/equipment/index/edit.js +2 -2
- package/addon/routes/maintenance/equipment/index/new.js +14 -1
- package/addon/routes/maintenance/equipment.js +14 -1
- package/addon/routes/maintenance/index.js +14 -1
- package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-forms.js +14 -1
- package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-submissions.js +14 -1
- package/addon/routes/maintenance/maintenances/index/details.js +2 -2
- package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
- package/addon/routes/maintenance/maintenances/index/new.js +14 -1
- package/addon/routes/maintenance/maintenances.js +14 -1
- package/addon/routes/maintenance/parts/index/details.js +2 -2
- package/addon/routes/maintenance/parts/index/edit.js +2 -2
- package/addon/routes/maintenance/parts/index/new.js +14 -1
- package/addon/routes/maintenance/parts.js +14 -1
- package/addon/routes/maintenance/schedules/index/details.js +2 -2
- package/addon/routes/maintenance/schedules/index/edit.js +2 -2
- package/addon/routes/maintenance/schedules/index/new.js +15 -1
- package/addon/routes/maintenance/schedules.js +15 -1
- package/addon/routes/maintenance/work-orders/index/details.js +2 -2
- package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
- package/addon/routes/maintenance/work-orders/index/new.js +14 -1
- package/addon/routes/maintenance/work-orders.js +14 -1
- package/addon/routes/management/fuel-transactions/index/details.js +9 -0
- package/addon/routes/management/fuel-transactions.js +14 -1
- package/addon/routes/management/index.js +13 -0
- package/addon/routes/management/trailers.js +16 -0
- package/addon/routes/management/vendors/integrated/details.js +14 -1
- package/addon/routes/management/vendors/integrated/edit.js +14 -1
- package/addon/routes/management/vendors/integrated/new.js +14 -1
- package/addon/routes/management/vendors/integrated.js +14 -1
- package/addon/routes/operations/orders/index/details.js +1 -0
- package/addon/routes/operations/orders.js +40 -1
- package/addon/routes/operations/routes/index/details.js +14 -1
- package/addon/routes/operations/routes/index/new.js +13 -0
- package/addon/routes/operations/routes.js +14 -1
- package/addon/routes/operations/service-rates/index/new.js +11 -0
- package/addon/routes/operations/service-rates.js +14 -1
- package/addon/routes/settings/avatars.js +14 -1
- package/addon/routes/settings/custom-fields.js +14 -1
- package/addon/routes/settings/map.js +14 -1
- package/addon/routes/settings/notifications.js +11 -0
- package/addon/routes/settings/orchestrator.js +1 -1
- package/addon/routes/settings/payments/index.js +1 -1
- package/addon/routes/settings/routing.js +14 -1
- package/addon/routes/settings/scheduling.js +14 -1
- package/addon/routes/settings/telematics.js +21 -0
- package/addon/routes/virtual.js +11 -0
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +1 -0
- package/addon/services/customer-actions.js +2 -1
- package/addon/services/device-actions.js +1 -0
- package/addon/services/device-event-actions.js +1 -0
- package/addon/services/driver-actions.js +4 -13
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/fleet-actions.js +1 -0
- package/addon/services/fuel-report-actions.js +1 -0
- package/addon/services/inspection-form-actions.js +1 -0
- package/addon/services/inspection-submission-actions.js +1 -0
- package/addon/services/integrated-vendor-actions.js +1 -0
- package/addon/services/issue-actions.js +1 -0
- package/addon/services/maintenance-actions.js +1 -0
- package/addon/services/maintenance-schedule-actions.js +2 -1
- package/addon/services/order-actions.js +7 -0
- package/addon/services/order-presentation.js +129 -0
- package/addon/services/part-actions.js +1 -0
- package/addon/services/place-actions.js +1 -0
- package/addon/services/sensor-actions.js +1 -0
- package/addon/services/service-area-actions.js +1 -0
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/services/telematic-actions.js +1 -0
- package/addon/services/trailer-actions.js +4 -14
- package/addon/services/vehicle-actions.js +4 -13
- package/addon/services/vendor-actions.js +1 -0
- package/addon/services/work-order-actions.js +1 -0
- package/addon/services/zone-actions.js +1 -0
- package/addon/templates/analytics/reports/index/details.hbs +1 -0
- package/addon/templates/analytics/reports/index.hbs +1 -0
- package/addon/templates/connectivity/devices/index/details.hbs +1 -0
- package/addon/templates/connectivity/devices/index.hbs +1 -0
- package/addon/templates/connectivity/events/index.hbs +1 -0
- package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
- package/addon/templates/connectivity/sensors/index.hbs +1 -0
- package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
- package/addon/templates/maintenance/equipment/index.hbs +2 -0
- package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details.hbs +1 -0
- package/addon/templates/maintenance/parts/index.hbs +2 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
- package/addon/templates/maintenance/schedules/index.hbs +2 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index.hbs +1 -0
- package/addon/templates/management/contacts/customers/details.hbs +1 -0
- package/addon/templates/management/contacts/customers.hbs +1 -0
- package/addon/templates/management/contacts/index/details.hbs +1 -0
- package/addon/templates/management/contacts/index.hbs +1 -0
- package/addon/templates/management/contacts.hbs +1 -0
- package/addon/templates/management/drivers/index/details.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +2 -0
- package/addon/templates/management/fleets/index/details.hbs +1 -0
- package/addon/templates/management/fleets/index.hbs +1 -0
- package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
- package/addon/templates/management/fuel-reports/index.hbs +1 -0
- package/addon/templates/management/fuel-transactions/index.hbs +1 -0
- package/addon/templates/management/issues/index/details.hbs +1 -0
- package/addon/templates/management/issues/index.hbs +1 -0
- package/addon/templates/management/places/index/details.hbs +1 -0
- package/addon/templates/management/places/index.hbs +1 -0
- package/addon/templates/management/trailers/index/details.hbs +1 -0
- package/addon/templates/management/trailers/index.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -0
- package/addon/templates/management/vehicles/index.hbs +2 -0
- package/addon/templates/management/vendors/index/details.hbs +1 -0
- package/addon/templates/management/vendors/index.hbs +2 -0
- package/addon/templates/management/vendors/integrated.hbs +1 -0
- package/addon/templates/operations/orders/index/details.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +1 -0
- package/addon/templates/operations/service-rates/index/details.hbs +1 -0
- package/addon/templates/operations/service-rates/index.hbs +1 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/addon/utils/normalize-order-config-flow.js +6 -1
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/map/marker-card/driver.js +1 -0
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/controllers/settings/telematics.js +1 -0
- package/app/routes/settings/telematics.js +1 -0
- package/app/services/order-presentation.js +1 -0
- package/app/templates/settings/telematics.js +1 -0
- package/composer.json +2 -2
- package/docs/AFAQY.md +1 -1
- package/docs/TELEMATICS_QUEUES.md +1 -1
- package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
- package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
- package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
- package/server/src/Http/Controllers/FleetOpsController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +56 -4
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
- package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
- package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
- package/server/src/Mail/CustomerCredentialsMail.php +1 -1
- package/server/src/Models/Order.php +35 -3
- package/server/src/Models/OrderConfig.php +129 -0
- package/server/src/Models/TrackingNumber.php +8 -4
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
- package/server/src/Support/Analytics/LiveFleet.php +18 -2
- package/server/src/Support/Authorization.php +103 -0
- package/server/src/Support/Database/TableIndexes.php +66 -0
- package/server/src/Support/OrderTracker.php +2 -2
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
- package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
- package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
- package/server/src/Support/Telematics/TelematicService.php +12 -2
- package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
- package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
- package/server/src/Tracking/TrackingOptions.php +2 -2
- package/server/src/Traits/AuthorizesMethods.php +37 -0
- package/server/src/routes.php +9 -1
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
- package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
- package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
- package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +1 -1
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/OrderConfigLifecycleTest.php +120 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/SettingControllerContractsTest.php +429 -18
- package/server/tests/SmallControllerContractsTest.php +8 -5
- package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
- package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
- package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
- package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
- package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
- package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
- package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
- package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
- package/translations/en-us.yaml +108 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Authorization;
|
|
4
|
+
use Fleetbase\FleetOps\Traits\AuthorizesMethods;
|
|
5
|
+
use Fleetbase\Models\User;
|
|
6
|
+
use Illuminate\Database\ConnectionResolver;
|
|
7
|
+
use Illuminate\Database\Eloquent\Model;
|
|
8
|
+
use Illuminate\Database\SQLiteConnection;
|
|
9
|
+
use Illuminate\Http\Exceptions\HttpResponseException;
|
|
10
|
+
use Illuminate\Http\Request;
|
|
11
|
+
use Illuminate\Routing\Controller;
|
|
12
|
+
|
|
13
|
+
if (!function_exists('Fleetbase\\Support\\auth')) {
|
|
14
|
+
eval('namespace Fleetbase\\Support; function auth() { return \\app("fleetops.authorization.test-auth"); }');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!function_exists('Fleetbase\\Support\\session')) {
|
|
18
|
+
eval('namespace Fleetbase\\Support; function session($key = null, $default = null) { return \\session($key, $default); }');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class FleetOpsAuthorizationUser extends User
|
|
22
|
+
{
|
|
23
|
+
public array $grantedPermissions = [];
|
|
24
|
+
public bool $permissionStoreUnavailable = false;
|
|
25
|
+
|
|
26
|
+
public function hasPermissionTo($permission, $guardName = null): bool
|
|
27
|
+
{
|
|
28
|
+
if ($this->permissionStoreUnavailable) {
|
|
29
|
+
throw new RuntimeException('Permission store unavailable');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return in_array($permission->name, $this->grantedPermissions, true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
beforeEach(function () {
|
|
37
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
38
|
+
$resolver = new ConnectionResolver(['mysql' => $connection]);
|
|
39
|
+
$resolver->setDefaultConnection('mysql');
|
|
40
|
+
Model::setConnectionResolver($resolver);
|
|
41
|
+
config()->set('auth.defaults.guard', 'sanctum');
|
|
42
|
+
config()->set('permission.table_names.permissions', 'permissions');
|
|
43
|
+
$connection->getSchemaBuilder()->create('permissions', function ($table) {
|
|
44
|
+
$table->string('id')->primary();
|
|
45
|
+
$table->string('name');
|
|
46
|
+
$table->string('guard_name')->default('sanctum');
|
|
47
|
+
});
|
|
48
|
+
$connection->table('permissions')->insert([
|
|
49
|
+
['id' => 'optimize', 'name' => 'fleet-ops optimize order'],
|
|
50
|
+
['id' => 'dispatch', 'name' => 'fleet-ops dispatch order'],
|
|
51
|
+
['id' => 'resource', 'name' => 'fleet-ops * order'],
|
|
52
|
+
['id' => 'service', 'name' => 'fleet-ops *'],
|
|
53
|
+
]);
|
|
54
|
+
session(['user' => null]);
|
|
55
|
+
$this->user = new FleetOpsAuthorizationUser();
|
|
56
|
+
$this->user->setRawAttributes(['type' => 'user'], true);
|
|
57
|
+
$this->auth = new class($this->user) {
|
|
58
|
+
public bool $unavailable = false;
|
|
59
|
+
|
|
60
|
+
public function __construct(public ?User $currentUser)
|
|
61
|
+
{
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public function user(): ?User
|
|
65
|
+
{
|
|
66
|
+
if ($this->unavailable) {
|
|
67
|
+
throw new RuntimeException('Session unavailable');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return $this->currentUser;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
app()->instance('fleetops.authorization.test-auth', $this->auth);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('operation authorization accepts exact and wildcard grants', function (string $grant) {
|
|
77
|
+
$this->user->grantedPermissions = [$grant];
|
|
78
|
+
|
|
79
|
+
expect(Authorization::canAny('optimize order'))->toBeTrue();
|
|
80
|
+
Authorization::authorize('optimize order');
|
|
81
|
+
})->with(['fleet-ops optimize order', 'fleet-ops * order', 'fleet-ops *']);
|
|
82
|
+
|
|
83
|
+
test('operation authorization accepts any requested permission and rejects missing grants', function () {
|
|
84
|
+
$this->user->grantedPermissions = ['fleet-ops dispatch order'];
|
|
85
|
+
|
|
86
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse()
|
|
87
|
+
->and(Authorization::canAny('optimize order', 'dispatch order'))->toBeTrue()
|
|
88
|
+
->and(Authorization::canAny('list unseeded-resource'))->toBeTrue();
|
|
89
|
+
expect(fn () => Authorization::authorize('optimize order'))->toThrow(HttpResponseException::class);
|
|
90
|
+
|
|
91
|
+
$this->user->permissionStoreUnavailable = true;
|
|
92
|
+
expect(Authorization::canAny('dispatch order'))->toBeFalse();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('platform administrators pass operations while system settings reject ordinary users', function () {
|
|
96
|
+
expect(fn () => Authorization::authorizeAdmin())->toThrow(HttpResponseException::class);
|
|
97
|
+
$this->user->setAttribute('type', 'admin');
|
|
98
|
+
expect(Authorization::canAny('optimize order'))->toBeTrue();
|
|
99
|
+
Authorization::authorizeAdmin();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('authorization fails closed when the authenticated user is missing or cannot be read', function () {
|
|
103
|
+
$this->auth->currentUser = null;
|
|
104
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse();
|
|
105
|
+
expect(fn () => Authorization::authorizeAdmin())->toThrow(HttpResponseException::class);
|
|
106
|
+
$this->auth->unavailable = true;
|
|
107
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('controller middleware applies the mapped operation and administrator checks', function () {
|
|
111
|
+
$controller = new class extends Controller {
|
|
112
|
+
use AuthorizesMethods;
|
|
113
|
+
|
|
114
|
+
public function __construct()
|
|
115
|
+
{
|
|
116
|
+
$this->authorizeMethods(['optimize' => ['optimize order', 'dispatch order'], 'settings' => 'admin']);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
$middleware = $controller->getMiddleware();
|
|
120
|
+
$request = Request::create('/');
|
|
121
|
+
$this->user->grantedPermissions = ['fleet-ops dispatch order'];
|
|
122
|
+
|
|
123
|
+
expect($middleware[0]['options']['only'])->toBe(['optimize'])
|
|
124
|
+
->and($middleware[0]['middleware']($request, fn ($nextRequest) => $nextRequest))->toBe($request);
|
|
125
|
+
expect(fn () => $middleware[1]['middleware']($request, fn () => 'allowed'))->toThrow(HttpResponseException::class);
|
|
126
|
+
$this->user->setAttribute('type', 'admin');
|
|
127
|
+
expect($middleware[1]['middleware']($request, fn () => 'allowed'))->toBe('allowed');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('analytics controllers enforce authorization before invoking their next middleware', function (string $class) {
|
|
131
|
+
$connection = Model::getConnectionResolver()->connection();
|
|
132
|
+
$connection->table('permissions')->insert(['id' => 'analytics', 'name' => 'fleet-ops view analytics']);
|
|
133
|
+
$controller = new $class();
|
|
134
|
+
$middleware = $controller->getMiddleware()[0]['middleware'];
|
|
135
|
+
$request = Request::create('/analytics');
|
|
136
|
+
expect(fn () => $middleware($request, fn () => 'private analytics'))->toThrow(HttpResponseException::class);
|
|
137
|
+
$this->user->grantedPermissions = ['fleet-ops view analytics'];
|
|
138
|
+
expect($middleware($request, fn ($forwarded) => $forwarded))->toBe($request);
|
|
139
|
+
})->with([
|
|
140
|
+
Fleetbase\FleetOps\Http\Controllers\Internal\v1\AnalyticsController::class,
|
|
141
|
+
Fleetbase\FleetOps\Http\Controllers\Internal\v1\MetricsController::class,
|
|
142
|
+
]);
|
|
@@ -184,10 +184,21 @@ test('operational alert helpers query orders positions and settings', function (
|
|
|
184
184
|
$order = Order::where('uuid', 'order-1')->withoutGlobalScopes()->first();
|
|
185
185
|
expect($probe->callHelper('latestPositionForOrder', $order))->toBeInstanceOf(Position::class);
|
|
186
186
|
|
|
187
|
-
$settings = $probe->callHelper('alertSettings');
|
|
187
|
+
$settings = $probe->callHelper('alertSettings', $order);
|
|
188
188
|
expect($settings['late_departures']['enabled'])->toBeTrue()
|
|
189
189
|
->and($settings['route_deviations']['distance_threshold_meters'])->toBe(500)
|
|
190
190
|
->and($settings['prolonged_stoppages']['duration_threshold_minutes'])->toBe(30);
|
|
191
|
+
|
|
192
|
+
// Settings resolve from the order's company without a company session
|
|
193
|
+
$connection->table('settings')->insert([
|
|
194
|
+
'key' => 'company.company-1.tracking',
|
|
195
|
+
'value' => json_encode(['alerts' => ['late_departures' => ['enabled' => false], 'route_deviations' => ['distance_threshold_meters' => 750]]]),
|
|
196
|
+
]);
|
|
197
|
+
session(['company' => null]);
|
|
198
|
+
$settings = $probe->callHelper('alertSettings', $order);
|
|
199
|
+
session(['company' => 'company-1']);
|
|
200
|
+
expect($settings['late_departures']['enabled'])->toBeFalse()
|
|
201
|
+
->and($settings['route_deviations']['distance_threshold_meters'])->toBe(750);
|
|
191
202
|
});
|
|
192
203
|
|
|
193
204
|
test('route point collection normalizes pairs and measures distances', function () {
|
|
@@ -384,13 +384,22 @@ test('index driver resource counts assigned orders and names the current one', f
|
|
|
384
384
|
});
|
|
385
385
|
|
|
386
386
|
test('shift change listener reads company scheduling settings', function () {
|
|
387
|
-
fleetopsMoreSeamBoot();
|
|
387
|
+
$connection = fleetopsMoreSeamBoot();
|
|
388
388
|
|
|
389
389
|
$class = Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class;
|
|
390
390
|
$listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
|
391
391
|
|
|
392
392
|
// With nothing stored the lookup falls back to the empty default
|
|
393
|
-
expect(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings'))->toBe([]);
|
|
393
|
+
expect(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', ['company-seam-2']))->toBe([]);
|
|
394
|
+
|
|
395
|
+
// Queued listeners have no company session, so the schedule's company is used
|
|
396
|
+
$connection->table('settings')->insert(['key' => 'company.company-seam-3.fleet-ops.scheduling-settings', 'value' => json_encode(['notify_drivers_on_shift_change' => true])]);
|
|
397
|
+
session(['company' => null]);
|
|
398
|
+
$settings = fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', ['company-seam-3']);
|
|
399
|
+
session(['company' => 'company-seam-2']);
|
|
400
|
+
|
|
401
|
+
expect($settings)->toBe(['notify_drivers_on_shift_change' => true])
|
|
402
|
+
->and(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', [null]))->toBe([]);
|
|
394
403
|
});
|
|
395
404
|
|
|
396
405
|
test('order insights capability builds its own company-scoped order query', function () {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
|
|
4
|
+
use Illuminate\Support\Carbon;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RetentionPolicy combines administrator policy with allowed company history
|
|
8
|
+
* preferences, enforces maximums, and caches policies for long-lived workers.
|
|
9
|
+
*/
|
|
10
|
+
function fleetopsRetentionResolver(array $system, array $company, ?array &$calls = null): void
|
|
11
|
+
{
|
|
12
|
+
$calls = [];
|
|
13
|
+
RetentionPolicy::$settingsResolver = function (string $scope, string $key, mixed $default, ?string $companyUuid) use ($system, $company, &$calls) {
|
|
14
|
+
$calls[] = [$scope, $key, $companyUuid];
|
|
15
|
+
expect($key)->toBe(RetentionPolicy::SETTING_KEY);
|
|
16
|
+
|
|
17
|
+
return $scope === 'system' ? $system : ($company[$companyUuid] ?? $default);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
beforeEach(function () {
|
|
22
|
+
RetentionPolicy::flush();
|
|
23
|
+
RetentionPolicy::$settingsResolver = null;
|
|
24
|
+
config(['telematics.telemetry' => ['event_retention_days' => 45, 'processed_retention_hours' => 48, 'log_telemetry_activity' => true, 'poll_queue' => 'default']]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(function () {
|
|
28
|
+
RetentionPolicy::flush();
|
|
29
|
+
RetentionPolicy::$settingsResolver = null;
|
|
30
|
+
Carbon::setTestNow();
|
|
31
|
+
config(['telematics.telemetry' => []]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('defaults come from package config, overridden by the admin setting, with everything clamped', function () {
|
|
35
|
+
fleetopsRetentionResolver([], []);
|
|
36
|
+
expect(RetentionPolicy::defaults())->toBe([
|
|
37
|
+
'event_retention_days' => 45,
|
|
38
|
+
'event_compact_after_days' => 7,
|
|
39
|
+
'position_retention_days' => 90,
|
|
40
|
+
'processed_retention_hours' => 48,
|
|
41
|
+
'quarantine_retention_days' => 7,
|
|
42
|
+
'sync_run_retention_days' => 7,
|
|
43
|
+
'max_event_retention_days' => 0,
|
|
44
|
+
'max_position_retention_days' => 0,
|
|
45
|
+
'log_telemetry_activity' => true,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
RetentionPolicy::flush();
|
|
49
|
+
fleetopsRetentionResolver([
|
|
50
|
+
'event_retention_days' => 99999,
|
|
51
|
+
'event_compact_after_days' => '',
|
|
52
|
+
'position_retention_days' => 0,
|
|
53
|
+
'quarantine_retention_days' => -5,
|
|
54
|
+
'processed_retention_hours' => null,
|
|
55
|
+
'log_telemetry_activity' => 'no',
|
|
56
|
+
'ignored' => 'value',
|
|
57
|
+
], []);
|
|
58
|
+
$defaults = RetentionPolicy::defaults();
|
|
59
|
+
expect($defaults['event_retention_days'])->toBe(3650)
|
|
60
|
+
->and($defaults['event_compact_after_days'])->toBe(7)
|
|
61
|
+
->and($defaults['position_retention_days'])->toBe(0)
|
|
62
|
+
->and($defaults['quarantine_retention_days'])->toBe(1)
|
|
63
|
+
->and($defaults['processed_retention_hours'])->toBe(48)
|
|
64
|
+
->and($defaults['log_telemetry_activity'])->toBeFalse()
|
|
65
|
+
->and($defaults)->not->toHaveKey('ignored')
|
|
66
|
+
->and(RetentionPolicy::keys())->toBe(array_keys(RetentionPolicy::FALLBACKS));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('company policies layer over the defaults and are cached until flushed', function () {
|
|
70
|
+
fleetopsRetentionResolver(['position_retention_days' => 30], ['company-1' => ['event_retention_days' => 10, 'log_telemetry_activity' => false]], $calls);
|
|
71
|
+
|
|
72
|
+
$policy = RetentionPolicy::forCompany('company-1');
|
|
73
|
+
expect($policy->get('event_retention_days'))->toBe(10)
|
|
74
|
+
->and($policy->get('position_retention_days'))->toBe(30)
|
|
75
|
+
->and($policy->get('processed_retention_hours'))->toBe(48)
|
|
76
|
+
->and($policy->logsTelemetryActivity())->toBeTrue()
|
|
77
|
+
->and($policy->toArray())->toHaveKeys(RetentionPolicy::keys())
|
|
78
|
+
->and(RetentionPolicy::forCompany('company-1'))->toBe($policy)
|
|
79
|
+
->and(RetentionPolicy::forCompany(null)->toArray())->toBe(RetentionPolicy::defaults())
|
|
80
|
+
->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(45)
|
|
81
|
+
->and(array_column($calls, 0))->toBe(['system', 'company', 'company']);
|
|
82
|
+
|
|
83
|
+
RetentionPolicy::flush('company-1');
|
|
84
|
+
RetentionPolicy::forCompany('company-1');
|
|
85
|
+
RetentionPolicy::forCompany('company-2');
|
|
86
|
+
expect(count($calls))->toBe(4);
|
|
87
|
+
|
|
88
|
+
RetentionPolicy::flush();
|
|
89
|
+
RetentionPolicy::forCompany('company-2');
|
|
90
|
+
expect(array_column($calls, 0))->toBe(['system', 'company', 'company', 'company', 'system', 'company']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('company history respects admin maximums and cannot override infrastructure or logging', function () {
|
|
94
|
+
fleetopsRetentionResolver([
|
|
95
|
+
'event_retention_days' => 0,
|
|
96
|
+
'position_retention_days' => 90,
|
|
97
|
+
'max_event_retention_days' => 30,
|
|
98
|
+
'max_position_retention_days' => 45,
|
|
99
|
+
'log_telemetry_activity' => false,
|
|
100
|
+
], [
|
|
101
|
+
'company-1' => [
|
|
102
|
+
'event_retention_days' => 0,
|
|
103
|
+
'position_retention_days' => 120,
|
|
104
|
+
'max_event_retention_days' => 0,
|
|
105
|
+
'max_position_retention_days' => 0,
|
|
106
|
+
'event_compact_after_days' => 0,
|
|
107
|
+
'processed_retention_hours' => 0,
|
|
108
|
+
'quarantine_retention_days' => 0,
|
|
109
|
+
'sync_run_retention_days' => 0,
|
|
110
|
+
'log_telemetry_activity' => true,
|
|
111
|
+
],
|
|
112
|
+
'company-2' => ['event_retention_days' => 5, 'position_retention_days' => 15],
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
$policy = RetentionPolicy::forCompany('company-1');
|
|
116
|
+
expect($policy->get('event_retention_days'))->toBe(30)
|
|
117
|
+
->and($policy->get('position_retention_days'))->toBe(45)
|
|
118
|
+
->and($policy->get('event_compact_after_days'))->toBe(7)
|
|
119
|
+
->and($policy->get('processed_retention_hours'))->toBe(48)
|
|
120
|
+
->and($policy->get('quarantine_retention_days'))->toBe(7)
|
|
121
|
+
->and($policy->get('sync_run_retention_days'))->toBe(7)
|
|
122
|
+
->and($policy->logsTelemetryActivity())->toBeFalse()
|
|
123
|
+
->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(5)
|
|
124
|
+
->and(RetentionPolicy::forCompany('company-2')->get('position_retention_days'))->toBe(15)
|
|
125
|
+
->and(RetentionPolicy::forCompany('inherited')->get('event_retention_days'))->toBe(30)
|
|
126
|
+
->and(RetentionPolicy::forCompany('inherited')->get('position_retention_days'))->toBe(45)
|
|
127
|
+
->and(RetentionPolicy::forCompany(null)->get('event_retention_days'))->toBe(30);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('unconfigured maximums preserve unlimited history and null preferences inherit defaults', function () {
|
|
131
|
+
fleetopsRetentionResolver([], ['company-1' => ['event_retention_days' => 0, 'position_retention_days' => null]]);
|
|
132
|
+
|
|
133
|
+
expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(0)
|
|
134
|
+
->and(RetentionPolicy::forCompany('company-1')->get('position_retention_days'))->toBe(90)
|
|
135
|
+
->and(RetentionPolicy::companyPreferences(['event_retention_days' => 'invalid', 'position_retention_days' => false]))->toBe([])
|
|
136
|
+
->and(RetentionPolicy::companyPreferences(['event_retention_days' => null, 'position_retention_days' => '', 'log_telemetry_activity' => true]))->toBe([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('constrained preferences keep zero explicit when allowed and replace it when an administrator imposes a maximum', function () {
|
|
140
|
+
$preferences = ['event_retention_days' => 0, 'position_retention_days' => null];
|
|
141
|
+
expect(RetentionPolicy::constrainCompanyPreferences($preferences, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 0]);
|
|
142
|
+
|
|
143
|
+
$defaults = array_replace(RetentionPolicy::FALLBACKS, ['max_event_retention_days' => 90, 'max_position_retention_days' => 360]);
|
|
144
|
+
$bounded = RetentionPolicy::constrainCompanyPreferences($preferences, $defaults);
|
|
145
|
+
expect($bounded)->toBe(['event_retention_days' => 90])
|
|
146
|
+
->and(RetentionPolicy::constrainCompanyPreferences(['event_retention_days' => 30, 'position_retention_days' => 720], $defaults))->toBe(['event_retention_days' => 30, 'position_retention_days' => 360])
|
|
147
|
+
->and(RetentionPolicy::constrainCompanyPreferences([], $defaults))->toBe([])
|
|
148
|
+
->and(RetentionPolicy::constrainCompanyPreferences($bounded, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 90]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('cutoffs, compaction and deletion rules follow the clamped values', function () {
|
|
152
|
+
Carbon::setTestNow('2026-09-23 12:00:00 UTC');
|
|
153
|
+
|
|
154
|
+
$policy = RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 10, 'processed_retention_hours' => 6, 'position_retention_days' => 0]);
|
|
155
|
+
expect($policy->deletesEvents())->toBeTrue()
|
|
156
|
+
->and($policy->compactsEvents())->toBeFalse()
|
|
157
|
+
->and($policy->cutoff('processed_retention_hours')?->toDateTimeString())->toBe('2026-09-23 06:00:00')
|
|
158
|
+
->and($policy->cutoff('event_retention_days')?->toDateTimeString())->toBe('2026-09-13 12:00:00')
|
|
159
|
+
->and($policy->cutoff('position_retention_days'))->toBeNull()
|
|
160
|
+
->and($policy->cutoff('unknown'))->toBeNull()
|
|
161
|
+
->and($policy->get('unknown'))->toBeNull();
|
|
162
|
+
|
|
163
|
+
expect(RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 3])->compactsEvents())->toBeTrue()
|
|
164
|
+
->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->compactsEvents())->toBeTrue()
|
|
165
|
+
->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->deletesEvents())->toBeFalse()
|
|
166
|
+
->and(RetentionPolicy::fromArray(['event_compact_after_days' => 0])->compactsEvents())->toBeFalse()
|
|
167
|
+
->and(RetentionPolicy::fromArray([], ['event_retention_days' => 12])->get('event_retention_days'))->toBe(12)
|
|
168
|
+
->and(RetentionPolicy::normalize(['log_telemetry_activity' => '1'], [])['log_telemetry_activity'])->toBeTrue();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('a failing or malformed settings store falls back to the layer below', function () {
|
|
172
|
+
RetentionPolicy::$settingsResolver = fn () => throw new RuntimeException('settings unavailable');
|
|
173
|
+
expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(45);
|
|
174
|
+
|
|
175
|
+
RetentionPolicy::flush();
|
|
176
|
+
RetentionPolicy::$settingsResolver = fn () => 'not-an-array';
|
|
177
|
+
expect(RetentionPolicy::defaults()['processed_retention_hours'])->toBe(48);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('company lookups scope the session for the setting store and restore it afterwards', function () {
|
|
181
|
+
// Without a resolver the policy reaches Setting, which has no database here and throws.
|
|
182
|
+
app('session')->put('company', 'previous-company');
|
|
183
|
+
expect(RetentionPolicy::forCompany('company-9')->get('event_retention_days'))->toBe(45)
|
|
184
|
+
->and(app('session')->get('company'))->toBe('previous-company');
|
|
185
|
+
|
|
186
|
+
app('session')->forget('company');
|
|
187
|
+
RetentionPolicy::flush();
|
|
188
|
+
expect(RetentionPolicy::forCompany('company-9')->get('processed_retention_hours'))->toBe(48)
|
|
189
|
+
->and(app('session')->has('company'))->toBeFalse();
|
|
190
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
|
|
4
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\TelemetryActivity;
|
|
5
|
+
use Spatie\Activitylog\ActivityLogger;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Telemetry writes bypass the activity log unless an administrator enabled it, and
|
|
9
|
+
* still run when no activity logger is available.
|
|
10
|
+
*/
|
|
11
|
+
function fleetopsTelemetryActivityLogger(): ActivityLogger
|
|
12
|
+
{
|
|
13
|
+
$logger = new class extends ActivityLogger {
|
|
14
|
+
public int $suppressed = 0;
|
|
15
|
+
|
|
16
|
+
public function __construct()
|
|
17
|
+
{
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public function withoutLogs(Closure $callback): mixed
|
|
21
|
+
{
|
|
22
|
+
$this->suppressed++;
|
|
23
|
+
|
|
24
|
+
return $callback();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
app()->instance(ActivityLogger::class, $logger);
|
|
28
|
+
|
|
29
|
+
return $logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(function () {
|
|
33
|
+
RetentionPolicy::flush();
|
|
34
|
+
config(['telematics.telemetry' => []]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(function () {
|
|
38
|
+
RetentionPolicy::flush();
|
|
39
|
+
RetentionPolicy::$settingsResolver = null;
|
|
40
|
+
app()->forgetInstance(ActivityLogger::class);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('telemetry activity follows the system policy and ignores legacy company overrides', function () {
|
|
44
|
+
RetentionPolicy::$settingsResolver = fn (string $scope, string $key, mixed $default, ?string $company) => $company === 'verbose' ? ['log_telemetry_activity' => true] : [];
|
|
45
|
+
$logger = fleetopsTelemetryActivityLogger();
|
|
46
|
+
|
|
47
|
+
expect(TelemetryActivity::run('quiet', fn () => 'saved'))->toBe('saved')
|
|
48
|
+
->and($logger->suppressed)->toBe(1)
|
|
49
|
+
->and(TelemetryActivity::run('verbose', fn () => 'saved'))->toBe('saved')
|
|
50
|
+
->and($logger->suppressed)->toBe(2)
|
|
51
|
+
->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
|
|
52
|
+
->and($logger->suppressed)->toBe(3);
|
|
53
|
+
|
|
54
|
+
RetentionPolicy::flush();
|
|
55
|
+
RetentionPolicy::$settingsResolver = fn (string $scope) => ['log_telemetry_activity' => $scope === 'system'];
|
|
56
|
+
|
|
57
|
+
expect(TelemetryActivity::run('quiet', fn () => 'logged'))->toBe('logged')
|
|
58
|
+
->and(TelemetryActivity::run('verbose', fn () => 'logged'))->toBe('logged')
|
|
59
|
+
->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
|
|
60
|
+
->and($logger->suppressed)->toBe(3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('telemetry writes still run when the activity logger cannot be resolved', function () {
|
|
64
|
+
RetentionPolicy::$settingsResolver = fn () => [];
|
|
65
|
+
app()->forgetInstance(ActivityLogger::class);
|
|
66
|
+
|
|
67
|
+
expect(TelemetryActivity::run('company-1', fn () => 'unlogged'))->toBe('unlogged');
|
|
68
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
if (!function_exists('Fleetbase\Models\session')) {
|
|
4
|
+
eval('namespace Fleetbase\Models; function session($key = null, $default = null) { if ($key === null) { return new class { public function has($k) { return \session($k) !== null; } public function get($k, $d = null) { return \session($k, $d); } public function missing($k) { return \session($k) === null; } }; } return \session($key, $default); }');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
8
|
+
use Fleetbase\FleetOps\Support\OrderTracker;
|
|
9
|
+
use Fleetbase\FleetOps\Tracking\TrackingIntelligenceService;
|
|
10
|
+
use Fleetbase\FleetOps\Tracking\TrackingOptions;
|
|
11
|
+
use Illuminate\Database\ConnectionResolver;
|
|
12
|
+
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
13
|
+
use Illuminate\Database\SQLiteConnection;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Covers company tracking settings resolution for TrackingOptions: the
|
|
17
|
+
* order's company is used when there is no company session (queued jobs,
|
|
18
|
+
* console commands), and the session company remains the fallback.
|
|
19
|
+
*/
|
|
20
|
+
function fleetopsTrackingOptionsBoot(): SQLiteConnection
|
|
21
|
+
{
|
|
22
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
23
|
+
$resolver = new ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
24
|
+
$resolver->setDefaultConnection('mysql');
|
|
25
|
+
EloquentModel::setConnectionResolver($resolver);
|
|
26
|
+
|
|
27
|
+
$connection->getSchemaBuilder()->create('settings', function ($blueprint) {
|
|
28
|
+
$blueprint->increments('id');
|
|
29
|
+
$blueprint->string('key')->nullable();
|
|
30
|
+
$blueprint->text('value')->nullable();
|
|
31
|
+
$blueprint->timestamps();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$connection->table('settings')->insert([
|
|
35
|
+
['key' => 'company.company-a.tracking', 'value' => json_encode(['provider' => 'osrm', 'cache_ttl_seconds' => 15])],
|
|
36
|
+
['key' => 'company.company-b.tracking', 'value' => json_encode(['provider' => 'calculated', 'cache_ttl_seconds' => 45])],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
session(['company' => null]);
|
|
40
|
+
|
|
41
|
+
return $connection;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test('tracking options read the given company settings regardless of session', function (?string $sessionCompany) {
|
|
45
|
+
fleetopsTrackingOptionsBoot();
|
|
46
|
+
session(['company' => $sessionCompany]);
|
|
47
|
+
|
|
48
|
+
$options = TrackingOptions::fromArray([], 'company-a');
|
|
49
|
+
|
|
50
|
+
expect($options->provider)->toBe('osrm')
|
|
51
|
+
->and($options->cacheTtlSeconds)->toBe(15);
|
|
52
|
+
|
|
53
|
+
// Explicit options still win over company settings
|
|
54
|
+
expect(TrackingOptions::fromArray(['provider' => 'google'], 'company-a')->provider)->toBe('google');
|
|
55
|
+
})->with([null, 'company-b']);
|
|
56
|
+
|
|
57
|
+
test('tracking options fall back to the session company when no company is given', function () {
|
|
58
|
+
fleetopsTrackingOptionsBoot();
|
|
59
|
+
session(['company' => 'company-b']);
|
|
60
|
+
|
|
61
|
+
$options = TrackingOptions::fromArray([]);
|
|
62
|
+
session(['company' => null]);
|
|
63
|
+
|
|
64
|
+
expect($options->provider)->toBe('calculated')
|
|
65
|
+
->and($options->cacheTtlSeconds)->toBe(45);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('order tracker builds options from the order company', function () {
|
|
69
|
+
fleetopsTrackingOptionsBoot();
|
|
70
|
+
|
|
71
|
+
$service = new class extends TrackingIntelligenceService {
|
|
72
|
+
public ?TrackingOptions $received = null;
|
|
73
|
+
|
|
74
|
+
public function __construct()
|
|
75
|
+
{
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public function track(Order $order, array|TrackingOptions $options = []): array
|
|
79
|
+
{
|
|
80
|
+
$this->received = $options;
|
|
81
|
+
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
app()->instance(TrackingIntelligenceService::class, $service);
|
|
86
|
+
|
|
87
|
+
$order = new Order();
|
|
88
|
+
$order->setRawAttributes(['uuid' => 'order-tracking-options', 'company_uuid' => 'company-a'], true);
|
|
89
|
+
|
|
90
|
+
(new OrderTracker($order))->toArray();
|
|
91
|
+
app()->forgetInstance(TrackingIntelligenceService::class);
|
|
92
|
+
|
|
93
|
+
expect($service->received)->toBeInstanceOf(TrackingOptions::class)
|
|
94
|
+
->and($service->received->provider)->toBe('osrm');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('tracking remains available with configured defaults when the settings database is unavailable', function () {
|
|
98
|
+
$connection = fleetopsTrackingOptionsBoot();
|
|
99
|
+
$connection->getSchemaBuilder()->drop('settings');
|
|
100
|
+
$original = config('fleetops.tracking', []);
|
|
101
|
+
try {
|
|
102
|
+
config(['fleetops.tracking' => ['provider' => 'calculated', 'cache_ttl_seconds' => 17]]);
|
|
103
|
+
$options = TrackingOptions::fromArray([], 'uncached-unavailable-company');
|
|
104
|
+
expect($options->provider)->toBe('calculated')->and($options->cacheTtlSeconds)->toBe(17);
|
|
105
|
+
expect(TrackingOptions::fromArray(['provider' => 'osrm'], 'uncached-unavailable-company')->provider)->toBe('osrm');
|
|
106
|
+
} finally {
|
|
107
|
+
config(['fleetops.tracking' => $original]);
|
|
108
|
+
}
|
|
109
|
+
});
|