@fleetbase/fleetops-engine 0.6.61 → 0.6.62
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 +23 -8
- package/addon/services/map-settings.js +4 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/migrations/2026_09_05_000001_add_unique_indexes_to_fleet_membership_pivots.php +129 -0
- package/server/src/Exceptions/PublicRelationNotFoundException.php +47 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +42 -7
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesPublicExpansions.php +187 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +93 -57
- package/server/src/Http/Controllers/Api/v1/EntityController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/FleetController.php +370 -33
- package/server/src/Http/Controllers/Api/v1/FuelReportController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +134 -35
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +1 -1
- package/server/src/Http/Filter/Concerns/ResolvesPublicRelationUuids.php +54 -0
- package/server/src/Http/Filter/DriverFilter.php +71 -15
- package/server/src/Http/Filter/FleetFilter.php +32 -33
- package/server/src/Http/Filter/VehicleFilter.php +57 -19
- package/server/src/Http/Requests/Concerns/ScopesPublicRelationRules.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +100 -9
- package/server/src/Http/Requests/CreateFleetRequest.php +30 -2
- package/server/src/Http/Requests/CreateServiceRateRequest.php +7 -2
- package/server/src/Http/Requests/CreateVehicleRequest.php +150 -31
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +110 -0
- package/server/src/Http/Resources/v1/Driver.php +22 -2
- package/server/src/Http/Resources/v1/Fleet.php +38 -25
- package/server/src/Http/Resources/v1/Vehicle.php +46 -4
- package/server/src/Models/Driver.php +2 -2
- package/server/src/routes.php +8 -0
- package/server/tests/ApiDriverControllerContractsTest.php +284 -13
- package/server/tests/ApiEntityControllerContractsTest.php +1 -1
- package/server/tests/ApiEquipmentControllerContractsTest.php +2 -2
- package/server/tests/ApiFleetControllerContractsTest.php +354 -21
- package/server/tests/ApiFuelReportControllerContractsTest.php +1 -1
- package/server/tests/ApiIssueControllerContractsTest.php +1 -1
- package/server/tests/ApiOrderControllerContractsTest.php +1 -1
- package/server/tests/ApiPartControllerContractsTest.php +2 -2
- package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +2 -2
- package/server/tests/ApiTrackingStatusControllerContractsTest.php +1 -1
- package/server/tests/ApiVehicleControllerContractsTest.php +175 -8
- package/server/tests/ControllerFilterContractsTest.php +132 -13
- package/server/tests/ControllerHelperContractsTest.php +6 -3
- package/server/tests/DriverFilterExecutionTest.php +130 -6
- package/server/tests/ExceptionContractsTest.php +15 -0
- package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +1 -1
- package/server/tests/Feature/Http/Api/FleetMembershipMigrationTest.php +140 -0
- package/server/tests/Feature/Http/Api/FleetMembershipTest.php +333 -0
- package/server/tests/Feature/Http/Api/FleetPublicContractTest.php +439 -0
- package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +2 -2
- package/server/tests/Feature/Http/Api/NullRelationshipInputTest.php +191 -0
- package/server/tests/Feature/Http/Api/PublicIdentifierFilterTest.php +293 -0
- package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +7 -1
- package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +3 -1
- package/server/tests/Feature/Http/Api/VehiclePublicContractTest.php +454 -0
- package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +1 -1
- package/server/tests/FleetHierarchyResourceTest.php +56 -8
- package/server/tests/RequestContractsTest.php +100 -7
- package/server/tests/Unit/Http/Resources/FleetResourceTest.php +50 -6
- package/server/tests/VehicleControllerHelperContractsTest.php +1 -1
|
@@ -3,23 +3,60 @@
|
|
|
3
3
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\FleetController;
|
|
4
4
|
use Fleetbase\FleetOps\Http\Requests\CreateFleetRequest;
|
|
5
5
|
use Fleetbase\FleetOps\Http\Requests\UpdateFleetRequest;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
6
7
|
use Fleetbase\FleetOps\Models\Fleet;
|
|
8
|
+
use Fleetbase\FleetOps\Models\ServiceArea;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Vendor;
|
|
11
|
+
use Fleetbase\FleetOps\Models\Zone;
|
|
12
|
+
use Fleetbase\Models\File;
|
|
7
13
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
8
14
|
use Illuminate\Http\Request;
|
|
9
15
|
|
|
10
16
|
class FleetOpsApiFleetControllerProbe extends FleetController
|
|
11
17
|
{
|
|
12
|
-
public ?Fleet $fleet
|
|
13
|
-
public
|
|
14
|
-
public
|
|
15
|
-
public
|
|
16
|
-
public
|
|
18
|
+
public ?Fleet $fleet = null;
|
|
19
|
+
public ?Vehicle $vehicle = null;
|
|
20
|
+
public ?Driver $driver = null;
|
|
21
|
+
public array $createdFleets = [];
|
|
22
|
+
public array $relationLookups = [];
|
|
23
|
+
public array $unresolvable = [];
|
|
24
|
+
public array $parentChain = [];
|
|
25
|
+
public array $membershipCalls = [];
|
|
26
|
+
public mixed $queryResults = null;
|
|
27
|
+
public bool $fleetNotFound = false;
|
|
28
|
+
public bool $resourceNotFound = false;
|
|
29
|
+
|
|
30
|
+
public function inputForTest(Request $request): array
|
|
31
|
+
{
|
|
32
|
+
return $this->fleetInputFromRequest($request);
|
|
33
|
+
}
|
|
17
34
|
|
|
18
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Stand in for the company-scoped public-id lookup.
|
|
37
|
+
*
|
|
38
|
+
* Anything listed in `$unresolvable` behaves as a cross-company or missing
|
|
39
|
+
* identifier does in production: nothing is found inside the caller's own
|
|
40
|
+
* company, and the lookup raises.
|
|
41
|
+
*/
|
|
42
|
+
protected function resolveUuid(string $modelClass, ?string $id, ?string $companyUuid = null): ?string
|
|
19
43
|
{
|
|
20
|
-
|
|
44
|
+
if (empty($id)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
$this->relationLookups[] = [$modelClass, $id];
|
|
21
49
|
|
|
22
|
-
|
|
50
|
+
if (in_array($id, $this->unresolvable, true)) {
|
|
51
|
+
throw (new ModelNotFoundException())->setModel($modelClass, $id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return $id . '-uuid';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected function parentUuidOf(string $uuid): ?string
|
|
58
|
+
{
|
|
59
|
+
return $this->parentChain[$uuid] ?? null;
|
|
23
60
|
}
|
|
24
61
|
|
|
25
62
|
protected function createFleet(array $input): Fleet
|
|
@@ -27,7 +64,7 @@ class FleetOpsApiFleetControllerProbe extends FleetController
|
|
|
27
64
|
$this->createdFleets[] = $input;
|
|
28
65
|
|
|
29
66
|
$fleet = new FleetOpsApiFleetFake();
|
|
30
|
-
$fleet->setRawAttributes(array_merge(['uuid' => 'created-fleet-uuid'], $input));
|
|
67
|
+
$fleet->setRawAttributes(array_merge(['uuid' => 'created-fleet-uuid', 'public_id' => 'fleet_created'], $input));
|
|
31
68
|
|
|
32
69
|
return $fleet;
|
|
33
70
|
}
|
|
@@ -43,6 +80,49 @@ class FleetOpsApiFleetControllerProbe extends FleetController
|
|
|
43
80
|
return $this->fleet;
|
|
44
81
|
}
|
|
45
82
|
|
|
83
|
+
protected function findVehicle(string $id): Vehicle
|
|
84
|
+
{
|
|
85
|
+
if ($this->resourceNotFound) {
|
|
86
|
+
throw new ModelNotFoundException();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return $this->vehicle;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected function findDriver(string $id): Driver
|
|
93
|
+
{
|
|
94
|
+
if ($this->resourceNotFound) {
|
|
95
|
+
throw new ModelNotFoundException();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return $this->driver;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected function withPublicRelations(Fleet $fleet): Fleet
|
|
102
|
+
{
|
|
103
|
+
return $fleet;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected function assignVehicleToFleet(Fleet $fleet, Vehicle $vehicle): void
|
|
107
|
+
{
|
|
108
|
+
$this->membershipCalls[] = ['assign-vehicle', $fleet->public_id, $vehicle->public_id];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
protected function removeVehicleFromFleet(Fleet $fleet, Vehicle $vehicle): void
|
|
112
|
+
{
|
|
113
|
+
$this->membershipCalls[] = ['remove-vehicle', $fleet->public_id, $vehicle->public_id];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected function assignDriverToFleet(Fleet $fleet, Driver $driver): void
|
|
117
|
+
{
|
|
118
|
+
$this->membershipCalls[] = ['assign-driver', $fleet->public_id, $driver->public_id];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected function removeDriverFromFleet(Fleet $fleet, Driver $driver): void
|
|
122
|
+
{
|
|
123
|
+
$this->membershipCalls[] = ['remove-driver', $fleet->public_id, $driver->public_id];
|
|
124
|
+
}
|
|
125
|
+
|
|
46
126
|
protected function queryFleets(Request $request)
|
|
47
127
|
{
|
|
48
128
|
return $this->queryResults ?? [['uuid' => 'fleet-uuid']];
|
|
@@ -90,6 +170,14 @@ class FleetOpsApiFleetFake extends Fleet
|
|
|
90
170
|
}
|
|
91
171
|
}
|
|
92
172
|
|
|
173
|
+
class FleetOpsApiFleetVehicleFake extends Vehicle
|
|
174
|
+
{
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class FleetOpsApiFleetDriverFake extends Driver
|
|
178
|
+
{
|
|
179
|
+
}
|
|
180
|
+
|
|
93
181
|
function fleetopsCreateFleetRequest(array $input): CreateFleetRequest
|
|
94
182
|
{
|
|
95
183
|
return CreateFleetRequest::create('/api/v1/fleets', 'POST', $input);
|
|
@@ -100,6 +188,14 @@ function fleetopsUpdateFleetRequest(array $input): UpdateFleetRequest
|
|
|
100
188
|
return UpdateFleetRequest::create('/api/v1/fleets/fleet-public', 'PUT', $input);
|
|
101
189
|
}
|
|
102
190
|
|
|
191
|
+
function fleetopsFleetFake(array $attributes): FleetOpsApiFleetFake
|
|
192
|
+
{
|
|
193
|
+
$fleet = new FleetOpsApiFleetFake();
|
|
194
|
+
$fleet->setRawAttributes($attributes);
|
|
195
|
+
|
|
196
|
+
return $fleet;
|
|
197
|
+
}
|
|
198
|
+
|
|
103
199
|
test('api fleet controller creates fleets with service area resolution', function () {
|
|
104
200
|
session(['company' => 'company-uuid']);
|
|
105
201
|
|
|
@@ -111,28 +207,191 @@ test('api fleet controller creates fleets with service area resolution', functio
|
|
|
111
207
|
'ignored' => 'not copied',
|
|
112
208
|
]));
|
|
113
209
|
|
|
210
|
+
// The pre-expansion contract — name plus service area — still behaves the
|
|
211
|
+
// way clients written against it expect.
|
|
114
212
|
expect($response['resource'])->toBe('fleet')
|
|
115
|
-
->and($controller->
|
|
116
|
-
[
|
|
117
|
-
'service_areas',
|
|
118
|
-
[
|
|
119
|
-
'public_id' => 'service-area-public',
|
|
120
|
-
'company_uuid' => 'company-uuid',
|
|
121
|
-
],
|
|
122
|
-
],
|
|
213
|
+
->and($controller->relationLookups)->toBe([
|
|
214
|
+
[ServiceArea::class, 'service-area-public'],
|
|
123
215
|
])
|
|
124
216
|
->and($controller->createdFleets[0])->toBe([
|
|
125
217
|
'name' => 'Downtown Fleet',
|
|
218
|
+
'service_area_uuid' => 'service-area-public-uuid',
|
|
126
219
|
'company_uuid' => 'company-uuid',
|
|
127
|
-
'service_area_uuid' => 'service-area-uuid',
|
|
128
220
|
]);
|
|
129
221
|
});
|
|
130
222
|
|
|
223
|
+
test('api fleet controller accepts every safe fleet field and resolves each relationship', function () {
|
|
224
|
+
session(['company' => 'company-uuid']);
|
|
225
|
+
|
|
226
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
227
|
+
|
|
228
|
+
$controller->create(fleetopsCreateFleetRequest([
|
|
229
|
+
'name' => 'Carpool',
|
|
230
|
+
'color' => '#2563EB',
|
|
231
|
+
'task' => 'Employee transport',
|
|
232
|
+
'status' => 'active',
|
|
233
|
+
'parent_fleet' => 'fleet_parent123',
|
|
234
|
+
'vendor' => 'vendor_123',
|
|
235
|
+
'service_area' => 'service_area_123',
|
|
236
|
+
'zone' => 'zone_123',
|
|
237
|
+
'photo' => 'file_123',
|
|
238
|
+
]));
|
|
239
|
+
|
|
240
|
+
expect($controller->createdFleets[0])->toBe([
|
|
241
|
+
'name' => 'Carpool',
|
|
242
|
+
'color' => '#2563EB',
|
|
243
|
+
'task' => 'Employee transport',
|
|
244
|
+
'status' => 'active',
|
|
245
|
+
'service_area_uuid' => 'service_area_123-uuid',
|
|
246
|
+
'zone_uuid' => 'zone_123-uuid',
|
|
247
|
+
'vendor_uuid' => 'vendor_123-uuid',
|
|
248
|
+
'parent_fleet_uuid' => 'fleet_parent123-uuid',
|
|
249
|
+
'image_uuid' => 'file_123-uuid',
|
|
250
|
+
'company_uuid' => 'company-uuid',
|
|
251
|
+
])->and($controller->relationLookups)->toBe([
|
|
252
|
+
[ServiceArea::class, 'service_area_123'],
|
|
253
|
+
[Zone::class, 'zone_123'],
|
|
254
|
+
[Vendor::class, 'vendor_123'],
|
|
255
|
+
[Fleet::class, 'fleet_parent123'],
|
|
256
|
+
[File::class, 'file_123'],
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('api fleet controller input excludes tenancy raw relation and generated columns', function () {
|
|
261
|
+
session(['company' => 'company-uuid']);
|
|
262
|
+
|
|
263
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
264
|
+
|
|
265
|
+
$input = $controller->inputForTest(new Request([
|
|
266
|
+
'name' => 'Carpool',
|
|
267
|
+
'company_uuid' => 'someone-elses-company',
|
|
268
|
+
'uuid' => 'forged',
|
|
269
|
+
'_key' => 'forged',
|
|
270
|
+
'public_id' => 'fleet_forged',
|
|
271
|
+
'slug' => 'forged',
|
|
272
|
+
'service_area_uuid' => 'forged',
|
|
273
|
+
'zone_uuid' => 'forged',
|
|
274
|
+
'vendor_uuid' => 'forged',
|
|
275
|
+
'parent_fleet_uuid' => 'forged',
|
|
276
|
+
'image_uuid' => 'forged',
|
|
277
|
+
]));
|
|
278
|
+
|
|
279
|
+
expect(array_keys($input))->toBe(['name']);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('api fleet controller creates a root fleet when no parent is supplied', function () {
|
|
283
|
+
session(['company' => 'company-uuid']);
|
|
284
|
+
|
|
285
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
286
|
+
$controller->create(fleetopsCreateFleetRequest(['name' => 'Root Fleet']));
|
|
287
|
+
|
|
288
|
+
expect($controller->createdFleets[0])->not->toHaveKey('parent_fleet_uuid');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('api fleet controller clears the parent fleet when it is sent null', function () {
|
|
292
|
+
session(['company' => 'company-uuid']);
|
|
293
|
+
|
|
294
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
295
|
+
$controller->fleet = fleetopsFleetFake([
|
|
296
|
+
'uuid' => 'child-uuid',
|
|
297
|
+
'public_id' => 'fleet_child',
|
|
298
|
+
'parent_fleet_uuid' => 'parent-uuid',
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
$controller->update('fleet_child', fleetopsUpdateFleetRequest(['parent_fleet' => null]));
|
|
302
|
+
|
|
303
|
+
expect($controller->fleet->updates[0])->toBe(['parent_fleet_uuid' => null])
|
|
304
|
+
->and($controller->relationLookups)->toBe([]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('api fleet controller rejects a fleet that names itself as its parent', function () {
|
|
308
|
+
session(['company' => 'company-uuid']);
|
|
309
|
+
|
|
310
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
311
|
+
$controller->fleet = fleetopsFleetFake(['uuid' => 'fleet_self-uuid', 'public_id' => 'fleet_self']);
|
|
312
|
+
|
|
313
|
+
$response = $controller->update('fleet_self', fleetopsUpdateFleetRequest(['parent_fleet' => 'fleet_self']));
|
|
314
|
+
|
|
315
|
+
expect($response)->toBe([
|
|
316
|
+
'json' => ['error' => 'A fleet cannot be its own parent fleet.'],
|
|
317
|
+
'status' => 422,
|
|
318
|
+
])->and($controller->fleet->updates)->toBe([]);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('api fleet controller rejects a parent that sits below the fleet in the tree', function () {
|
|
322
|
+
session(['company' => 'company-uuid']);
|
|
323
|
+
|
|
324
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
325
|
+
$controller->fleet = fleetopsFleetFake(['uuid' => 'root-uuid', 'public_id' => 'fleet_root']);
|
|
326
|
+
|
|
327
|
+
// grandchild -> child -> root: making the grandchild the root's parent
|
|
328
|
+
// would close the loop.
|
|
329
|
+
$controller->parentChain = [
|
|
330
|
+
'fleet_grandchild-uuid' => 'fleet_child-uuid',
|
|
331
|
+
'fleet_child-uuid' => 'root-uuid',
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
$response = $controller->update('fleet_root', fleetopsUpdateFleetRequest([
|
|
335
|
+
'parent_fleet' => 'fleet_grandchild',
|
|
336
|
+
]));
|
|
337
|
+
|
|
338
|
+
expect($response)->toBe([
|
|
339
|
+
'json' => ['error' => 'A fleet cannot be assigned beneath one of its own subfleets.'],
|
|
340
|
+
'status' => 422,
|
|
341
|
+
])->and($controller->fleet->updates)->toBe([]);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('api fleet controller accepts a parent that is not an ancestor of the fleet', function () {
|
|
345
|
+
session(['company' => 'company-uuid']);
|
|
346
|
+
|
|
347
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
348
|
+
$controller->fleet = fleetopsFleetFake(['uuid' => 'fleet-uuid', 'public_id' => 'fleet_child']);
|
|
349
|
+
|
|
350
|
+
$controller->parentChain = ['fleet_parent-uuid' => 'unrelated-root-uuid'];
|
|
351
|
+
|
|
352
|
+
$controller->update('fleet_child', fleetopsUpdateFleetRequest(['parent_fleet' => 'fleet_parent']));
|
|
353
|
+
|
|
354
|
+
expect($controller->fleet->updates[0])->toBe(['parent_fleet_uuid' => 'fleet_parent-uuid']);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('api fleet controller rejects relationships that belong to another company', function () {
|
|
358
|
+
session(['company' => 'company-uuid']);
|
|
359
|
+
|
|
360
|
+
foreach (['parent_fleet', 'vendor', 'zone', 'service_area'] as $relation) {
|
|
361
|
+
$expected = [
|
|
362
|
+
'json' => ['error' => 'No ' . str_replace('_', ' ', $relation) . ' resource found for the identifier provided.'],
|
|
363
|
+
'status' => 404,
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
367
|
+
$controller->unresolvable = ['other_company_id'];
|
|
368
|
+
|
|
369
|
+
$created = $controller->create(fleetopsCreateFleetRequest([
|
|
370
|
+
'name' => 'Carpool',
|
|
371
|
+
$relation => 'other_company_id',
|
|
372
|
+
]));
|
|
373
|
+
|
|
374
|
+
$updateController = new FleetOpsApiFleetControllerProbe();
|
|
375
|
+
$updateController->unresolvable = ['other_company_id'];
|
|
376
|
+
$updateController->fleet = fleetopsFleetFake(['uuid' => 'fleet-uuid', 'public_id' => 'fleet_123']);
|
|
377
|
+
|
|
378
|
+
$updated = $updateController->update('fleet_123', fleetopsUpdateFleetRequest([
|
|
379
|
+
$relation => 'other_company_id',
|
|
380
|
+
]));
|
|
381
|
+
|
|
382
|
+
// A cross-company identifier is answered exactly as a missing one, so
|
|
383
|
+
// the response cannot be used to discover another organization's data.
|
|
384
|
+
// Update answers the same way create does, and writes nothing.
|
|
385
|
+
expect($created)->toBe($expected)
|
|
386
|
+
->and($updated)->toBe($expected)
|
|
387
|
+
->and($updateController->fleet->updates)->toBe([]);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
131
391
|
test('api fleet controller updates queries finds and deletes fleets', function () {
|
|
132
392
|
session(['company' => 'company-uuid']);
|
|
133
393
|
|
|
134
|
-
$fleet =
|
|
135
|
-
$fleet->setRawAttributes(['uuid' => 'fleet-uuid', 'name' => 'Old Fleet']);
|
|
394
|
+
$fleet = fleetopsFleetFake(['uuid' => 'fleet-uuid', 'name' => 'Old Fleet']);
|
|
136
395
|
|
|
137
396
|
$controller = new FleetOpsApiFleetControllerProbe();
|
|
138
397
|
$controller->fleet = $fleet;
|
|
@@ -149,7 +408,7 @@ test('api fleet controller updates queries finds and deletes fleets', function (
|
|
|
149
408
|
expect($updated)->toBe(['resource' => 'fleet', 'fleet' => $fleet])
|
|
150
409
|
->and($fleet->updates[0])->toBe([
|
|
151
410
|
'name' => 'Updated Fleet',
|
|
152
|
-
'service_area_uuid' => 'service-area-uuid',
|
|
411
|
+
'service_area_uuid' => 'service-area-public-uuid',
|
|
153
412
|
])
|
|
154
413
|
->and($query)->toBe([
|
|
155
414
|
'collection' => 'fleet',
|
|
@@ -174,3 +433,77 @@ test('api fleet controller returns missing fleet responses for update find and d
|
|
|
174
433
|
->and($controller->find('missing-fleet', new Request()))->toBe($expected)
|
|
175
434
|
->and($controller->delete('missing-fleet', new Request()))->toBe($expected);
|
|
176
435
|
});
|
|
436
|
+
|
|
437
|
+
test('api fleet controller answers membership changes with a stable public id shape', function () {
|
|
438
|
+
session(['company' => 'company-uuid']);
|
|
439
|
+
|
|
440
|
+
$vehicle = new FleetOpsApiFleetVehicleFake();
|
|
441
|
+
$vehicle->setRawAttributes(['uuid' => 'vehicle-uuid', 'public_id' => 'vehicle_123']);
|
|
442
|
+
|
|
443
|
+
$driver = new FleetOpsApiFleetDriverFake();
|
|
444
|
+
$driver->setRawAttributes(['uuid' => 'driver-uuid', 'public_id' => 'driver_123']);
|
|
445
|
+
|
|
446
|
+
$controller = new FleetOpsApiFleetControllerProbe();
|
|
447
|
+
$controller->fleet = fleetopsFleetFake(['uuid' => 'fleet-uuid', 'public_id' => 'fleet_123']);
|
|
448
|
+
$controller->vehicle = $vehicle;
|
|
449
|
+
$controller->driver = $driver;
|
|
450
|
+
|
|
451
|
+
// All four operations answer in the same shape, and never with a uuid.
|
|
452
|
+
expect($controller->assignVehicle('fleet_123', 'vehicle_123'))->toBe([
|
|
453
|
+
'json' => ['fleet' => 'fleet_123', 'vehicle' => 'vehicle_123', 'assigned' => true],
|
|
454
|
+
'status' => 200,
|
|
455
|
+
])->and($controller->removeVehicle('fleet_123', 'vehicle_123'))->toBe([
|
|
456
|
+
'json' => ['fleet' => 'fleet_123', 'vehicle' => 'vehicle_123', 'assigned' => false],
|
|
457
|
+
'status' => 200,
|
|
458
|
+
])->and($controller->assignDriver('fleet_123', 'driver_123'))->toBe([
|
|
459
|
+
'json' => ['fleet' => 'fleet_123', 'driver' => 'driver_123', 'assigned' => true],
|
|
460
|
+
'status' => 200,
|
|
461
|
+
])->and($controller->removeDriver('fleet_123', 'driver_123'))->toBe([
|
|
462
|
+
'json' => ['fleet' => 'fleet_123', 'driver' => 'driver_123', 'assigned' => false],
|
|
463
|
+
'status' => 200,
|
|
464
|
+
])->and($controller->membershipCalls)->toBe([
|
|
465
|
+
['assign-vehicle', 'fleet_123', 'vehicle_123'],
|
|
466
|
+
['remove-vehicle', 'fleet_123', 'vehicle_123'],
|
|
467
|
+
['assign-driver', 'fleet_123', 'driver_123'],
|
|
468
|
+
['remove-driver', 'fleet_123', 'driver_123'],
|
|
469
|
+
]);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test('api fleet controller treats an unavailable fleet or resource as not found for membership', function () {
|
|
473
|
+
session(['company' => 'company-uuid']);
|
|
474
|
+
|
|
475
|
+
$missingFleet = new FleetOpsApiFleetControllerProbe();
|
|
476
|
+
$missingFleet->fleetNotFound = true;
|
|
477
|
+
|
|
478
|
+
$missingResource = new FleetOpsApiFleetControllerProbe();
|
|
479
|
+
$missingResource->fleet = fleetopsFleetFake(['uuid' => 'fleet-uuid', 'public_id' => 'fleet_123']);
|
|
480
|
+
$missingResource->resourceNotFound = true;
|
|
481
|
+
|
|
482
|
+
// A resource in another company is unavailable, not forbidden — the answer
|
|
483
|
+
// is the same one a caller gets for an id that does not exist at all.
|
|
484
|
+
expect($missingFleet->assignVehicle('fleet_other', 'vehicle_123'))->toBe([
|
|
485
|
+
'json' => ['error' => 'Fleet or vehicle resource not found.'],
|
|
486
|
+
'status' => 404,
|
|
487
|
+
])->and($missingFleet->removeDriver('fleet_other', 'driver_123'))->toBe([
|
|
488
|
+
'json' => ['error' => 'Fleet or driver resource not found.'],
|
|
489
|
+
'status' => 404,
|
|
490
|
+
])->and($missingResource->assignVehicle('fleet_123', 'vehicle_other'))->toBe([
|
|
491
|
+
'json' => ['error' => 'Fleet or vehicle resource not found.'],
|
|
492
|
+
'status' => 404,
|
|
493
|
+
])->and($missingResource->assignDriver('fleet_123', 'driver_other'))->toBe([
|
|
494
|
+
'json' => ['error' => 'Fleet or driver resource not found.'],
|
|
495
|
+
'status' => 404,
|
|
496
|
+
])
|
|
497
|
+
// Removal answers the same way assignment does when either side is
|
|
498
|
+
// unavailable, so all four operations stay consistent.
|
|
499
|
+
->and($missingResource->removeVehicle('fleet_123', 'vehicle_other'))->toBe([
|
|
500
|
+
'json' => ['error' => 'Fleet or vehicle resource not found.'],
|
|
501
|
+
'status' => 404,
|
|
502
|
+
])
|
|
503
|
+
->and($missingResource->removeDriver('fleet_123', 'driver_other'))->toBe([
|
|
504
|
+
'json' => ['error' => 'Fleet or driver resource not found.'],
|
|
505
|
+
'status' => 404,
|
|
506
|
+
])
|
|
507
|
+
->and($missingFleet->membershipCalls)->toBe([])
|
|
508
|
+
->and($missingResource->membershipCalls)->toBe([]);
|
|
509
|
+
});
|
|
@@ -17,7 +17,7 @@ class FleetOpsApiFuelReportControllerProbe extends FuelReportController
|
|
|
17
17
|
public bool $driverNotFound = false;
|
|
18
18
|
public bool $fuelReportNotFound = false;
|
|
19
19
|
|
|
20
|
-
protected function findDriverRecord(string $id): Driver
|
|
20
|
+
protected function findDriverRecord(?string $id): Driver
|
|
21
21
|
{
|
|
22
22
|
if ($this->driverNotFound) {
|
|
23
23
|
throw new ModelNotFoundException();
|
|
@@ -17,7 +17,7 @@ class FleetOpsApiIssueControllerProbe extends IssueController
|
|
|
17
17
|
public bool $driverNotFound = false;
|
|
18
18
|
public bool $issueNotFound = false;
|
|
19
19
|
|
|
20
|
-
protected function findDriverRecord(string $id): Driver
|
|
20
|
+
protected function findDriverRecord(?string $id): Driver
|
|
21
21
|
{
|
|
22
22
|
if ($this->driverNotFound) {
|
|
23
23
|
throw new ModelNotFoundException();
|
|
@@ -80,7 +80,7 @@ class FleetOpsApiOrderCrudControllerProbe extends OrderController
|
|
|
80
80
|
return $this->payload = new FleetOpsApiOrderPayloadFake();
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
protected function findDriverByPublicId(string $publicId): ?Fleetbase\FleetOps\Models\Driver
|
|
83
|
+
protected function findDriverByPublicId(?string $publicId): ?Fleetbase\FleetOps\Models\Driver
|
|
84
84
|
{
|
|
85
85
|
$this->driver ??= new FleetOpsApiOrderDriverFake();
|
|
86
86
|
$this->driver->setRawAttributes([
|
|
@@ -26,7 +26,7 @@ class FleetOpsApiPartControllerProbe extends PartController
|
|
|
26
26
|
return $part;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
protected function resolveModel(string $modelClass, string $id): Illuminate\Database\Eloquent\Model
|
|
29
|
+
protected function resolveModel(string $modelClass, string $id, ?string $companyUuid = null): Illuminate\Database\Eloquent\Model
|
|
30
30
|
{
|
|
31
31
|
if ($this->partNotFound) {
|
|
32
32
|
throw new ModelNotFoundException();
|
|
@@ -37,7 +37,7 @@ class FleetOpsApiPartControllerProbe extends PartController
|
|
|
37
37
|
return $this->part;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
protected function resolveUuid(string $modelClass, ?string $id): ?string
|
|
40
|
+
protected function resolveUuid(string $modelClass, ?string $id, ?string $companyUuid = null): ?string
|
|
41
41
|
{
|
|
42
42
|
$this->resolvedUuids[] = [$modelClass, $id];
|
|
43
43
|
|
|
@@ -23,7 +23,7 @@ class FleetOpsApiServiceAreaControllerProbe extends ApiServiceAreaController
|
|
|
23
23
|
public bool $notFound = false;
|
|
24
24
|
public bool $createThrows = false;
|
|
25
25
|
|
|
26
|
-
protected function serviceAreaUuid(string $publicId, array $where): ?string
|
|
26
|
+
protected function serviceAreaUuid(?string $publicId, array $where): ?string
|
|
27
27
|
{
|
|
28
28
|
$this->uuidLookups[] = [$publicId, $where];
|
|
29
29
|
|
|
@@ -116,7 +116,7 @@ class FleetOpsApiZoneControllerProbe extends ApiZoneController
|
|
|
116
116
|
public mixed $queryResults = null;
|
|
117
117
|
public bool $notFound = false;
|
|
118
118
|
|
|
119
|
-
protected function serviceAreaUuid(string $publicId, array $where): ?string
|
|
119
|
+
protected function serviceAreaUuid(?string $publicId, array $where): ?string
|
|
120
120
|
{
|
|
121
121
|
$this->uuidLookups[] = [$publicId, $where];
|
|
122
122
|
|
|
@@ -32,7 +32,7 @@ class FleetOpsApiTrackingStatusControllerProbe extends TrackingStatusController
|
|
|
32
32
|
return 'tracking-number-uuid';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
protected function getOrderTrackingNumberUuid(string $orderId): ?string
|
|
35
|
+
protected function getOrderTrackingNumberUuid(?string $orderId): ?string
|
|
36
36
|
{
|
|
37
37
|
$this->orderLookups[] = $orderId;
|
|
38
38
|
|