@fleetbase/fleetops-engine 0.6.67 → 0.6.69
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 +11 -15
- package/addon/components/contact/form.hbs +13 -2
- package/addon/components/customer/details.hbs +17 -17
- package/addon/components/customer/form.hbs +13 -2
- package/addon/components/customer/form.js +0 -56
- package/addon/components/driver/details.hbs +13 -19
- package/addon/components/driver/form.hbs +20 -35
- package/addon/components/driver/form.js +0 -58
- package/addon/components/fuel-integration/form.hbs +2 -2
- package/addon/components/fuel-integration/form.js +8 -0
- package/addon/components/fuel-integration/hub.js +1 -1
- package/addon/components/modals/driver-form.hbs +2 -2
- package/addon/components/modals/reset-profile-credentials.hbs +23 -0
- package/addon/components/modals/reset-profile-credentials.js +70 -0
- package/addon/components/profile-identity-hint.hbs +13 -0
- package/addon/components/profile-identity-hint.js +49 -0
- package/addon/controllers/management/drivers/index.js +4 -0
- package/addon/extension.js +4 -2
- package/addon/services/contact-actions.js +50 -89
- package/addon/services/driver-actions.js +98 -0
- package/addon/templates/connectivity/fuel-providers/details/index.hbs +1 -1
- package/addon/utils/fuel-transaction.js +2 -0
- package/addon/utils/profile-login-actions.js +172 -0
- package/app/components/modals/{reset-customer-credentials.js → reset-profile-credentials.js} +1 -1
- package/app/components/profile-identity-hint.js +1 -0
- package/composer.json +1 -1
- package/docs/SAFEE.md +5 -0
- package/docs/TELEMATICS_QUEUES.md +59 -8
- package/extension.json +1 -1
- package/package.json +3 -3
- package/scripts/pest-bootstrap.php +12 -0
- package/server/config/fuel-providers.php +53 -0
- package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
- package/server/resources/views/mail/driver-credentials.blade.php +21 -0
- package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
- package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
- package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +5 -17
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +40 -4
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +182 -162
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +156 -27
- package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
- package/server/src/Http/Resources/v1/Contact.php +3 -0
- package/server/src/Http/Resources/v1/Driver.php +3 -0
- package/server/src/Jobs/PollTelematicTelemetry.php +36 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +2 -2
- package/server/src/Mail/DriverCredentialsMail.php +55 -0
- package/server/src/Models/Contact.php +55 -90
- package/server/src/Models/Sensor.php +3 -5
- package/server/src/Observers/ContactObserver.php +2 -22
- package/server/src/Observers/DriverObserver.php +5 -6
- package/server/src/Providers/FleetOpsServiceProvider.php +15 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
- package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
- package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
- package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
- package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
- package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
- package/server/src/Support/FuelProviders/Providers/SascoFuelProvider.php +272 -0
- package/server/src/Support/ProfileAccountManager.php +405 -0
- package/server/src/Support/Telematics/TelematicService.php +6 -4
- package/server/src/Support/Telematics/Telemetry/Inbox.php +27 -9
- package/server/src/routes.php +5 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
- package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
- package/server/tests/ApiDriverControllerContractsTest.php +102 -20
- package/server/tests/CommandContractsTest.php +4 -4
- package/server/tests/ControllerHelperContractsTest.php +3 -11
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +5 -0
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
- package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
- package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
- package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
- package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
- package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
- package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
- package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
- package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
- package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
- package/server/tests/Feature/Http/ManualTelemetryPollingTest.php +88 -4
- package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
- package/server/tests/ModelAccessorContractsTest.php +82 -20
- package/server/tests/ObserverContractsTest.php +27 -2
- package/server/tests/RequestContractsTest.php +19 -11
- package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
- package/server/tests/TelematicControllerContractsTest.php +22 -4
- package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
- package/server/tests/Unit/Models/ContactTest.php +48 -5
- package/server/tests/Unit/Models/SensorTest.php +2 -2
- package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
- package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
- package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
- package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
- package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
- package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
- package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
- package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
- package/server/tests/Unit/Support/SascoFuelProviderTest.php +338 -0
- package/translations/en-us.yaml +37 -2
- package/translations/uk-ua.yaml +3475 -0
- package/addon/components/modals/reset-customer-credentials.hbs +0 -18
- package/addon/components/modals/reset-customer-credentials.js +0 -55
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\ConnectionResolver;
|
|
4
|
+
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
5
|
+
use Illuminate\Database\SQLiteConnection;
|
|
6
|
+
use Illuminate\Support\Facades\DB;
|
|
7
|
+
use Illuminate\Support\Facades\Schema;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The migration that converts profile-only `user` accounts into managed
|
|
11
|
+
* driver/customer accounts, run up and down against in-memory SQLite.
|
|
12
|
+
*
|
|
13
|
+
* An account is converted only when it clearly is a profile login and nothing
|
|
14
|
+
* else; anything that looks like a team member is left alone, and down()
|
|
15
|
+
* reverts only the rows up() converted.
|
|
16
|
+
*/
|
|
17
|
+
function fleetopsProfileConversionBoot(bool $withPolicies = true): SQLiteConnection
|
|
18
|
+
{
|
|
19
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
20
|
+
$resolver = new ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
21
|
+
$resolver->setDefaultConnection('mysql');
|
|
22
|
+
EloquentModel::setConnectionResolver($resolver);
|
|
23
|
+
|
|
24
|
+
app()->instance('db', new class($connection) {
|
|
25
|
+
public function __construct(public SQLiteConnection $c)
|
|
26
|
+
{
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public function connection($name = null): SQLiteConnection
|
|
30
|
+
{
|
|
31
|
+
return $this->c;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public function table($table, $as = null)
|
|
35
|
+
{
|
|
36
|
+
return $this->c->table($table, $as);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function __call($method, $arguments)
|
|
40
|
+
{
|
|
41
|
+
return $this->c->{$method}(...$arguments);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
app()->instance('db.schema', $connection->getSchemaBuilder());
|
|
45
|
+
DB::clearResolvedInstance('db');
|
|
46
|
+
Schema::clearResolvedInstance('db.schema');
|
|
47
|
+
|
|
48
|
+
$schema = $connection->getSchemaBuilder();
|
|
49
|
+
$tables = [
|
|
50
|
+
'users' => ['uuid', 'type', 'meta'],
|
|
51
|
+
'drivers' => ['uuid', 'user_uuid'],
|
|
52
|
+
'contacts' => ['uuid', 'user_uuid', 'type'],
|
|
53
|
+
'companies' => ['uuid', 'owner_uuid'],
|
|
54
|
+
'company_users' => ['uuid', 'user_uuid', 'company_uuid'],
|
|
55
|
+
'roles' => ['name'],
|
|
56
|
+
'model_has_roles' => ['role_id', 'model_uuid'],
|
|
57
|
+
'model_has_permissions' => ['permission_id', 'model_uuid'],
|
|
58
|
+
];
|
|
59
|
+
if ($withPolicies) {
|
|
60
|
+
$tables['model_has_policies'] = ['policy_id', 'model_uuid'];
|
|
61
|
+
}
|
|
62
|
+
foreach ($tables as $table => $columns) {
|
|
63
|
+
$schema->create($table, function ($blueprint) use ($columns) {
|
|
64
|
+
$blueprint->increments('id');
|
|
65
|
+
foreach ($columns as $column) {
|
|
66
|
+
$blueprint->string($column)->nullable();
|
|
67
|
+
}
|
|
68
|
+
$blueprint->timestamp('deleted_at')->nullable();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
foreach (['Driver', 'Fleet-Ops Customer', 'Administrator'] as $role) {
|
|
73
|
+
$connection->table('roles')->insert(['name' => $role]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return $connection;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fleetopsProfileConversionMigration(): object
|
|
80
|
+
{
|
|
81
|
+
return require dirname(__DIR__, 4) . '/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Seed a user holding the given profile(s), roles on its company membership
|
|
86
|
+
* (or on the user itself), and optional direct permission or policy.
|
|
87
|
+
*/
|
|
88
|
+
function fleetopsProfileConversionUser(SQLiteConnection $connection, string $uuid, array $options = []): void
|
|
89
|
+
{
|
|
90
|
+
$connection->table('users')->insert(['uuid' => $uuid, 'type' => $options['type'] ?? 'user', 'meta' => $options['meta'] ?? null, 'deleted_at' => $options['deleted_at'] ?? null]);
|
|
91
|
+
$connection->table('company_users')->insert(['uuid' => 'cu-' . $uuid, 'user_uuid' => $uuid, 'company_uuid' => 'company-1']);
|
|
92
|
+
|
|
93
|
+
if ($options['driver'] ?? false) {
|
|
94
|
+
$connection->table('drivers')->insert(['uuid' => 'driver-' . $uuid, 'user_uuid' => $uuid]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ($options['customer'] ?? false) {
|
|
98
|
+
$connection->table('contacts')->insert(['uuid' => 'contact-' . $uuid, 'user_uuid' => $uuid, 'type' => 'customer']);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
foreach ($options['roles'] ?? [] as $role) {
|
|
102
|
+
$roleId = $connection->table('roles')->where('name', $role)->value('id');
|
|
103
|
+
$connection->table('model_has_roles')->insert(['role_id' => $roleId, 'model_uuid' => ($options['role_on_user'] ?? false) ? $uuid : 'cu-' . $uuid]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if ($options['permission'] ?? false) {
|
|
107
|
+
$connection->table('model_has_permissions')->insert(['permission_id' => 1, 'model_uuid' => 'cu-' . $uuid]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ($options['policy'] ?? false) {
|
|
111
|
+
$connection->table('model_has_policies')->insert(['policy_id' => 1, 'model_uuid' => $uuid]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function fleetopsProfileConversionTypes(SQLiteConnection $connection): array
|
|
116
|
+
{
|
|
117
|
+
return $connection->table('users')->orderBy('uuid')->pluck('type', 'uuid')->all();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test('up converts profile-only accounts and leaves anything that looks like a team member', function () {
|
|
121
|
+
$connection = fleetopsProfileConversionBoot();
|
|
122
|
+
|
|
123
|
+
fleetopsProfileConversionUser($connection, 'a-driver', ['driver' => true, 'roles' => ['Driver'], 'meta' => json_encode(['source' => 'console'])]);
|
|
124
|
+
fleetopsProfileConversionUser($connection, 'b-customer', ['customer' => true, 'roles' => ['Fleet-Ops Customer'], 'role_on_user' => true]);
|
|
125
|
+
fleetopsProfileConversionUser($connection, 'c-owner', ['driver' => true, 'roles' => ['Driver']]);
|
|
126
|
+
$connection->table('companies')->insert(['uuid' => 'company-1', 'owner_uuid' => 'c-owner']);
|
|
127
|
+
fleetopsProfileConversionUser($connection, 'd-extra-role', ['driver' => true, 'roles' => ['Driver', 'Administrator']]);
|
|
128
|
+
fleetopsProfileConversionUser($connection, 'e-permission', ['driver' => true, 'roles' => ['Driver'], 'permission' => true]);
|
|
129
|
+
fleetopsProfileConversionUser($connection, 'f-policy', ['customer' => true, 'roles' => ['Fleet-Ops Customer'], 'policy' => true]);
|
|
130
|
+
fleetopsProfileConversionUser($connection, 'g-both-profiles', ['driver' => true, 'customer' => true, 'roles' => ['Driver']]);
|
|
131
|
+
fleetopsProfileConversionUser($connection, 'h-no-roles', ['driver' => true]);
|
|
132
|
+
fleetopsProfileConversionUser($connection, 'i-wrong-role', ['customer' => true, 'roles' => ['Driver']]);
|
|
133
|
+
fleetopsProfileConversionUser($connection, 'j-no-profile', ['roles' => ['Driver']]);
|
|
134
|
+
fleetopsProfileConversionUser($connection, 'k-admin', ['type' => 'admin', 'driver' => true, 'roles' => ['Driver']]);
|
|
135
|
+
fleetopsProfileConversionUser($connection, 'l-deleted', ['driver' => true, 'roles' => ['Driver'], 'deleted_at' => '2026-01-01 00:00:00']);
|
|
136
|
+
fleetopsProfileConversionUser($connection, 'm-bad-meta', ['driver' => true, 'roles' => ['Driver'], 'meta' => 'not-json']);
|
|
137
|
+
|
|
138
|
+
fleetopsProfileConversionMigration()->up();
|
|
139
|
+
|
|
140
|
+
expect(fleetopsProfileConversionTypes($connection))->toBe([
|
|
141
|
+
'a-driver' => 'driver',
|
|
142
|
+
'b-customer' => 'customer',
|
|
143
|
+
'c-owner' => 'user',
|
|
144
|
+
'd-extra-role' => 'user',
|
|
145
|
+
'e-permission' => 'user',
|
|
146
|
+
'f-policy' => 'user',
|
|
147
|
+
'g-both-profiles' => 'user',
|
|
148
|
+
'h-no-roles' => 'user',
|
|
149
|
+
'i-wrong-role' => 'user',
|
|
150
|
+
'j-no-profile' => 'user',
|
|
151
|
+
'k-admin' => 'admin',
|
|
152
|
+
'l-deleted' => 'user',
|
|
153
|
+
'm-bad-meta' => 'driver',
|
|
154
|
+
])
|
|
155
|
+
// The previous type is recorded next to any existing meta
|
|
156
|
+
->and(json_decode($connection->table('users')->where('uuid', 'a-driver')->value('meta'), true))->toBe(['source' => 'console', 'previous_type' => 'user'])
|
|
157
|
+
->and(json_decode($connection->table('users')->where('uuid', 'm-bad-meta')->value('meta'), true))->toBe(['previous_type' => 'user']);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('down reverts only the accounts up converted', function () {
|
|
161
|
+
$connection = fleetopsProfileConversionBoot();
|
|
162
|
+
|
|
163
|
+
fleetopsProfileConversionUser($connection, 'a-driver', ['driver' => true, 'roles' => ['Driver'], 'meta' => json_encode(['source' => 'console'])]);
|
|
164
|
+
fleetopsProfileConversionUser($connection, 'b-customer', ['customer' => true, 'roles' => ['Fleet-Ops Customer']]);
|
|
165
|
+
// Managed accounts created after the change carry no previous type
|
|
166
|
+
fleetopsProfileConversionUser($connection, 'c-native-driver', ['type' => 'driver', 'driver' => true, 'meta' => json_encode(['source' => 'app'])]);
|
|
167
|
+
// A previous type other than `user` is not this migration's
|
|
168
|
+
fleetopsProfileConversionUser($connection, 'd-other-previous', ['type' => 'customer', 'customer' => true, 'meta' => json_encode(['previous_type' => 'contact'])]);
|
|
169
|
+
|
|
170
|
+
$migration = fleetopsProfileConversionMigration();
|
|
171
|
+
$migration->up();
|
|
172
|
+
$migration->down();
|
|
173
|
+
|
|
174
|
+
expect(fleetopsProfileConversionTypes($connection))->toBe([
|
|
175
|
+
'a-driver' => 'user',
|
|
176
|
+
'b-customer' => 'user',
|
|
177
|
+
'c-native-driver' => 'driver',
|
|
178
|
+
'd-other-previous' => 'customer',
|
|
179
|
+
])
|
|
180
|
+
->and(json_decode($connection->table('users')->where('uuid', 'a-driver')->value('meta'), true))->toBe(['source' => 'console'])
|
|
181
|
+
->and(json_decode($connection->table('users')->where('uuid', 'b-customer')->value('meta'), true))->toBe([])
|
|
182
|
+
->and(json_decode($connection->table('users')->where('uuid', 'd-other-previous')->value('meta'), true))->toBe(['previous_type' => 'contact']);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('up and down do nothing when the tables they read are missing', function () {
|
|
186
|
+
$connection = fleetopsProfileConversionBoot(false);
|
|
187
|
+
fleetopsProfileConversionUser($connection, 'a-driver', ['driver' => true, 'roles' => ['Driver']]);
|
|
188
|
+
|
|
189
|
+
$migration = fleetopsProfileConversionMigration();
|
|
190
|
+
$migration->up();
|
|
191
|
+
|
|
192
|
+
expect(fleetopsProfileConversionTypes($connection))->toBe(['a-driver' => 'user']);
|
|
193
|
+
|
|
194
|
+
$connection->table('users')->where('uuid', 'a-driver')->update(['type' => 'driver', 'meta' => json_encode(['previous_type' => 'user'])]);
|
|
195
|
+
$connection->getSchemaBuilder()->drop('users');
|
|
196
|
+
|
|
197
|
+
// Without a users table there is nothing to revert
|
|
198
|
+
$migration->down();
|
|
199
|
+
|
|
200
|
+
expect($connection->getSchemaBuilder()->hasTable('users'))->toBeFalse();
|
|
201
|
+
});
|
|
@@ -252,10 +252,12 @@ test('contact update guards types resolve users and portal seams persist', funct
|
|
|
252
252
|
$probe->callProtected('assertContactInputIsValid', [$request, &$input, $contact]);
|
|
253
253
|
})->toThrow(Exception::class);
|
|
254
254
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
// The login account is managed by the profile, so user references are dropped
|
|
256
|
+
$userInput = ['name' => 'Seam Contact', 'user_uuid' => '88888888-8888-4888-8888-888888888881', 'user' => 'user_contactseam'];
|
|
257
|
+
Closure::bind(function (array &$input) {
|
|
258
|
+
$this->resolveUserInput(Request::create('/x', 'PUT', []), $input);
|
|
259
|
+
}, $probe, ContactController::class)($userInput);
|
|
260
|
+
expect($userInput)->toBe(['name' => 'Seam Contact']);
|
|
259
261
|
|
|
260
262
|
// Portal seams read users, mint passwords, and persist meta quietly
|
|
261
263
|
expect($probe->callProtected('contactUser', [$contact])?->uuid)->toBe('88888888-8888-4888-8888-888888888881')
|
|
@@ -125,7 +125,9 @@ function fleetopsCustomerCredentialsBoot(): SQLiteConnection
|
|
|
125
125
|
'contacts' => ['uuid', 'public_id', 'company_uuid', 'user_uuid', 'name', 'email', 'phone', 'type', 'title', 'meta', 'photo_uuid', 'place_uuid', 'slug', '_key'],
|
|
126
126
|
'users' => ['uuid', 'public_id', 'company_uuid', 'name', 'email', 'phone', 'password', 'type', 'status', 'timezone', 'slug', 'username', 'avatar_uuid', 'meta', '_key'],
|
|
127
127
|
'companies' => ['uuid', 'public_id', 'name', 'country'],
|
|
128
|
-
|
|
128
|
+
// session_status reads the user's membership of the session company
|
|
129
|
+
'company_users' => ['uuid', 'company_uuid', 'user_uuid', 'status'],
|
|
130
|
+
'files' => ['uuid', 'public_id', 'company_uuid', 'subject_uuid', 'subject_type', 'name', 'path', 'disk', 'type', '_key'],
|
|
129
131
|
];
|
|
130
132
|
foreach ($tables as $table => $columns) {
|
|
131
133
|
$schema->create($table, function ($blueprint) use ($columns) {
|
|
@@ -192,5 +194,7 @@ test('customer resolution and payload helpers expose user details', function ()
|
|
|
192
194
|
|
|
193
195
|
$payload = $probe->callHelper('customerPayload', $customer);
|
|
194
196
|
expect($payload['uuid'])->toBe('55555555-5555-4555-8555-555555555555')
|
|
195
|
-
->and($payload)->
|
|
197
|
+
->and($payload['user']['uuid'])->toBe('user-1')
|
|
198
|
+
->and($payload['is_staff_linked'])->toBeFalse()
|
|
199
|
+
->and($payload['login_status'])->toBe('active');
|
|
196
200
|
});
|
|
@@ -1131,6 +1131,29 @@ test('internal driver controller verify code returns driver resource and handles
|
|
|
1131
1131
|
]);
|
|
1132
1132
|
});
|
|
1133
1133
|
|
|
1134
|
+
test('internal driver controller refuses phone login and code verification for deactivated drivers', function () {
|
|
1135
|
+
FleetOpsInternalDriverAuthControllerProbe::resetProbe();
|
|
1136
|
+
app()->instance('request', Request::create('/', 'POST', ['phone' => '15551234567']));
|
|
1137
|
+
|
|
1138
|
+
$deactivated = fleetopsInternalDriverAuthUser();
|
|
1139
|
+
$deactivated->setAttribute('status', 'inactive');
|
|
1140
|
+
FleetOpsInternalDriverAuthControllerProbe::$loginUser = $deactivated;
|
|
1141
|
+
FleetOpsInternalDriverAuthControllerProbe::$verificationUser = $deactivated;
|
|
1142
|
+
FleetOpsInternalDriverAuthControllerProbe::$verificationExists = true;
|
|
1143
|
+
|
|
1144
|
+
$controller = new FleetOpsInternalDriverAuthControllerProbe();
|
|
1145
|
+
$login = $controller->loginWithPhone();
|
|
1146
|
+
$verify = $controller->verifyCode(new Request(['identity' => '+15551234567', 'code' => '111111']));
|
|
1147
|
+
|
|
1148
|
+
expect($login->getStatusCode())->toBe(403)
|
|
1149
|
+
->and($login->getData(true))->toBe(['error' => 'This driver login has been deactivated.'])
|
|
1150
|
+
->and($verify->getStatusCode())->toBe(403)
|
|
1151
|
+
->and($verify->getData(true))->toBe(['error' => 'This driver login has been deactivated.'])
|
|
1152
|
+
// No code is sent and no token is issued
|
|
1153
|
+
->and(FleetOpsInternalDriverAuthControllerProbe::$verifications)->toBe([])
|
|
1154
|
+
->and(FleetOpsInternalDriverAuthControllerProbe::$tokens)->toBe([]);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1134
1157
|
test('internal driver controller delegates login and create driver verification flows to api controller', function () {
|
|
1135
1158
|
$request = new Request(['for' => 'create_driver']);
|
|
1136
1159
|
|
|
@@ -10,11 +10,12 @@ use Illuminate\Http\Request;
|
|
|
10
10
|
/**
|
|
11
11
|
* Covers the internal DriverController createRecord and updateRecord flows
|
|
12
12
|
* against an in-memory SQLite fixture. The creation closure executes end to
|
|
13
|
-
* end: company resolution,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* end: company resolution, managed account creation with driver typing
|
|
14
|
+
* through ProfileAccountManager, and the exception-to-error-response
|
|
15
|
+
* branches. Role assignment requires the full spatie permission boot, which
|
|
16
|
+
* this fixture leaves out, so flows settle in the documented error branch
|
|
17
|
+
* after the user record is persisted (see
|
|
18
|
+
* DriverControllerExistingUserAdoptionTest for the full flow).
|
|
18
19
|
*/
|
|
19
20
|
if (!function_exists('Fleetbase\Support\session')) {
|
|
20
21
|
eval('namespace Fleetbase\Support; 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); } }; } return \session($key, $default); }');
|
|
@@ -101,6 +102,28 @@ function fleetopsInternalDriverCreateBoot(): SQLiteConnection
|
|
|
101
102
|
}
|
|
102
103
|
});
|
|
103
104
|
Illuminate\Support\Facades\DB::clearResolvedInstance('db');
|
|
105
|
+
app()->instance('hash', new class implements Illuminate\Contracts\Hashing\Hasher {
|
|
106
|
+
public function info($hashedValue): array
|
|
107
|
+
{
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public function make($value, array $options = []): string
|
|
112
|
+
{
|
|
113
|
+
return 'hashed:' . $value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public function check($value, $hashedValue, array $options = []): bool
|
|
117
|
+
{
|
|
118
|
+
return 'hashed:' . $value === $hashedValue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public function needsRehash($hashedValue, array $options = []): bool
|
|
122
|
+
{
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
Illuminate\Support\Facades\Hash::clearResolvedInstance('hash');
|
|
104
127
|
|
|
105
128
|
$schema = $connection->getSchemaBuilder();
|
|
106
129
|
$tables = [
|
|
@@ -158,31 +181,33 @@ test('create record provisions a driver-typed user before role assignment', func
|
|
|
158
181
|
->and($connection->table('company_users')->count())->toBeGreaterThanOrEqual(0);
|
|
159
182
|
});
|
|
160
183
|
|
|
161
|
-
test('create record
|
|
184
|
+
test('create record ignores a picked user account and resolves the login from the email', function () {
|
|
162
185
|
$connection = fleetopsInternalDriverCreateBoot();
|
|
163
|
-
$connection->table('users')->insert(['uuid' => '11111111-1111-4111-8111-111111111111', 'company_uuid' => 'company-1', 'name' => 'Existing', 'type' => 'driver']);
|
|
164
|
-
$connection->table('drivers')->insert(['uuid' => 'driver-1', 'company_uuid' => 'company-1', 'user_uuid' => '11111111-1111-4111-8111-111111111111']);
|
|
186
|
+
$connection->table('users')->insert(['uuid' => '11111111-1111-4111-8111-111111111111', 'company_uuid' => 'company-1', 'name' => 'Existing', 'email' => 'existing@example.com', 'type' => 'driver']);
|
|
165
187
|
|
|
166
|
-
|
|
167
|
-
'name' => '
|
|
188
|
+
(new DriverController())->createRecord(fleetopsInternalDriverCreateRequest([
|
|
189
|
+
'name' => 'Someone Else',
|
|
190
|
+
'email' => 'someone@example.com',
|
|
168
191
|
'user_uuid' => '11111111-1111-4111-8111-111111111111',
|
|
169
192
|
]));
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
// The login account is managed by the profile: a new driver account is
|
|
195
|
+
// created for the email rather than the picked account being taken over
|
|
196
|
+
expect($connection->table('users')->count())->toBe(2)
|
|
197
|
+
->and($connection->table('users')->where('email', 'someone@example.com')->value('type'))->toBe('driver')
|
|
198
|
+
->and($connection->table('users')->where('uuid', '11111111-1111-4111-8111-111111111111')->value('name'))->toBe('Existing');
|
|
173
199
|
});
|
|
174
200
|
|
|
175
|
-
test('create record
|
|
201
|
+
test('create record applies photo avatars to the new driver account', function () {
|
|
176
202
|
$connection = fleetopsInternalDriverCreateBoot();
|
|
177
|
-
$connection->table('users')->insert(['uuid' => '11111111-1111-4111-8111-111111111111', 'company_uuid' => 'company-1', 'name' => 'Adoptable', 'type' => 'driver']);
|
|
178
203
|
|
|
179
204
|
$result = (new DriverController())->createRecord(fleetopsInternalDriverCreateRequest([
|
|
180
|
-
'name' => '
|
|
181
|
-
'user_uuid' => '11111111-1111-4111-8111-111111111111',
|
|
205
|
+
'name' => 'Photo Driver',
|
|
182
206
|
'photo_uuid' => '22222222-2222-4222-8222-222222222222',
|
|
183
207
|
]));
|
|
184
208
|
|
|
185
|
-
// Avatar
|
|
209
|
+
// Avatar is set when the account is created, before the harness
|
|
210
|
+
// role-assignment limitation.
|
|
186
211
|
expect($connection->table('users')->value('avatar_uuid'))->toBe('22222222-2222-4222-8222-222222222222')
|
|
187
212
|
->and($result)->toBeInstanceOf(JsonResponse::class)
|
|
188
213
|
->and($result->getData(true))->toHaveKey('error');
|