createUserSession(['list_own_subusers']); $subuser = api_fixtures()->createSubuser([ 'name' => 'Legacy Permission Driver', ]); $grantId = api_fixtures()->grantSubuser( $subuser['id'], $session['user']['customer_number'], ['VEHICLES_LIST'] ); $legacyPermissions = '0'; $statement = api_test_runtime()->db()->prepare( 'UPDATE `subuser_grants` SET `permissions` = ? WHERE `id` = ?' ); $statement->bind_param('si', $legacyPermissions, $grantId); $statement->execute(); $statement->close(); $response = api_client()->get('/subusers?page=1&limit=5&include_non_enabled=true', $session['headers']); $response ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $matchingSubusers = array_values(array_filter( is_array($response->data()) ? $response->data() : [], static fn (mixed $item): bool => is_array($item) && (int)($item['id'] ?? 0) === (int)$subuser['id'] )); expect($matchingSubusers)->toHaveCount(1); expect($matchingSubusers[0]['grant_permissions'] ?? null)->toBe([]); expect($matchingSubusers[0]['permissions'] ?? null)->toBe([]); }); it('enforces the password policy for setup without rejecting legacy valid passwords at login', function (): void { api_test_covers('POST /subusers/setup', 'failure'); api_test_covers('POST /subusers/auth/password', 'happy'); $subuser = api_fixtures()->createSubuser([ 'password_plaintext' => 'invalidpassword', ]); $setupResponse = api_client()->post('/subusers/setup', [ 'token' => 'policy-test-token', 'name' => 'Policy Driver', 'password' => 'invalidpassword', ]); $authResponse = api_client()->post('/subusers/auth/password', [ 'subuser_id' => $subuser['id'], 'password' => 'invalidpassword', ]); $setupResponse ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false) ->assertMessage(SUBUSER_PASSWORD_POLICY_MESSAGE); $authResponse ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($authResponse->data()['session'] ?? null)->toBeString(); (new \objects\subusers_o())->invalidateSessionToken((string)$authResponse->data()['session']); }); it('returns a generic error for invalid subuser credentials', function (): void { api_test_covers('POST /subusers/auth/password', 'failure'); $response = api_client()->post('/subusers/auth/password', [ 'subuser_id' => 999999999, 'password' => 'whatever', ]); $response ->assertStatus(401) ->assertEnvelope() ->assertSuccess(false) ->assertMessage('Invalid credentials'); }); it('requires subuser management access before exposing permission nodes', function (): void { api_test_covers('GET /subusers/permission-nodes', 'auth'); $unauthenticated = api_client()->get('/subusers/permission-nodes'); $unauthenticated ->assertStatus(401) ->assertEnvelope() ->assertSuccess(false); $session = api_fixtures()->createUserSession(['list_own_subusers']); $authorized = api_client()->get('/subusers/permission-nodes', $session['headers']); $authorized ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($authorized->data())->toBeArray()->not->toBeEmpty(); }); it('requires subuser management access before exposing simplified permission templates', function (): void { api_test_covers('GET /subusers/permission-templates', 'auth'); api_test_covers('GET /subusers/permission-templates', 'happy'); $unauthenticated = api_client()->get('/subusers/permission-templates'); $unauthenticated ->assertStatus(401) ->assertEnvelope() ->assertSuccess(false); $session = api_fixtures()->createUserSession(['list_own_subusers']); $authorized = api_client()->get('/subusers/permission-templates', $session['headers']); $authorized ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($authorized->data()['templates'] ?? null)->toBeArray()->not->toBeEmpty(); expect(array_column($authorized->data()['templates'], 'key')) ->toContain('driver') ->toContain('booking_coordinator') ->toContain('fleet_admin') ->toContain('deactivated'); expect($authorized->data()['groups'] ?? null)->toBeArray()->not->toBeEmpty(); }); it('rejects customer subuser listing without own-scope permission', function (): void { api_test_covers('GET /subusers', 'auth'); $session = api_fixtures()->createUserSession(['user']); $response = api_client()->get('/subusers?page=1&limit=5', $session['headers']); $response ->assertStatus(403) ->assertEnvelope() ->assertSuccess(false); }); it('replaces older setup tokens when a new setup token is generated', function (): void { $subuser = api_fixtures()->createSubuser([ 'password_plaintext' => null, 'name' => 'Pending Setup Driver', ]); $subuserObject = (new \objects\subusers_o())->select((int)$subuser['id']); $subuserObject->getObjectProperties(); $firstToken = $subuserObject->generateSetupToken(); $secondToken = $subuserObject->generateSetupToken(); try { expect((new \objects\subusers_o())->getSubuserBySetupToken($firstToken))->toBeNull(); expect((new \objects\subusers_o())->getSubuserBySetupToken($secondToken))->not->toBeNull(); } finally { (new \objects\subusers_o())->invalidateSetupToken($secondToken); } }); it('validates setup identifiers before setting the driver password', function (): void { api_test_covers('POST /subusers/setup', 'failure'); $existing = api_fixtures()->createSubuser([ 'username' => 'existing-driver-setup', ]); $pending = api_fixtures()->createSubuser([ 'password_plaintext' => null, 'username' => 'pending-driver-setup', 'name' => 'Pending Driver', ]); $pendingObject = (new \objects\subusers_o())->select((int)$pending['id']); $pendingObject->getObjectProperties(); $token = $pendingObject->generateSetupToken(); try { $response = api_client()->post('/subusers/setup', [ 'token' => $token, 'name' => 'Pending Driver', 'username' => $existing['username'], 'password' => 'ValidPass123', ]); $response ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false) ->assertMessage('Account already exists with this username'); $row = api_fixtures()->fetchRowById('subusers', (int)$pending['id']); expect($row['password'] ?? null)->toBeNull(); expect((new \objects\subusers_o())->getSubuserBySetupToken($token))->not->toBeNull(); } finally { (new \objects\subusers_o())->invalidateSetupToken($token); } }); it('lists chauffeur grants across customers for superusers', function (): void { $session = api_fixtures()->createUserSession(['list_subusers']); $firstCustomer = api_fixtures()->createUser([ 'display_name' => 'Fleet Customer Alpha', 'economic_customer_name' => 'Fleet Customer Alpha', ]); $secondCustomer = api_fixtures()->createUser([ 'display_name' => 'Fleet Customer Beta', 'economic_customer_name' => 'Fleet Customer Beta', ]); $firstSubuser = api_fixtures()->createSubuser(['name' => 'Alpha Driver']); $secondSubuser = api_fixtures()->createSubuser(['name' => 'Beta Driver']); $firstVehicle = api_fixtures()->createVehicle([ 'customer_id' => (int)$firstCustomer['customer_number'], 'type' => 1, 'reg' => 'ab12345', ]); $firstGrantId = api_fixtures()->grantSubuser( (int)$firstSubuser['id'], (int)$firstCustomer['customer_number'], ['VEHICLES_LIST', 'SUBUSERS_LIST', 'SELFSERVE_LIST', 'SELFSERVE_ADD'], ['assigned_vehicle_id' => (int)$firstVehicle['id']] ); $secondGrantId = api_fixtures()->grantSubuser( (int)$secondSubuser['id'], (int)$secondCustomer['customer_number'], ['BOOKINGS_LIST'] ); $sharedGrantId = api_fixtures()->grantSubuser( (int)$firstSubuser['id'], (int)$secondCustomer['customer_number'], ['ORDERS_LIST'] ); $response = api_client()->get('/superuser/subusers?page=1&limit=20&search=Driver', $session['headers']); $response ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $rows = array_values(array_filter( is_array($response->data()) ? $response->data() : [], static fn (mixed $item): bool => is_array($item) && in_array((int)($item['id'] ?? 0), [(int)$firstSubuser['id'], (int)$secondSubuser['id']], true) )); expect($rows)->toHaveCount(3); $byGrantId = []; foreach ($rows as $row) { $byGrantId[(int)$row['grant_id']] = $row; } expect($byGrantId[$firstGrantId]['id'])->toBe((int)$firstSubuser['id']); expect($byGrantId[$firstGrantId]['customer_number'])->toBe((int)$firstCustomer['customer_number']); expect($byGrantId[$firstGrantId]['assigned_vehicle_id'])->toBe((int)$firstVehicle['id']); expect($byGrantId[$firstGrantId]['assigned_vehicle_reg'])->toBe('AB12345'); expect($byGrantId[$firstGrantId]['dognvask_enabled'])->toBeTrue(); expect($byGrantId[$sharedGrantId]['id'])->toBe((int)$firstSubuser['id']); expect($byGrantId[$sharedGrantId]['customer_number'])->toBe((int)$secondCustomer['customer_number']); expect($byGrantId[$sharedGrantId]['dognvask_enabled'])->toBeFalse(); expect($byGrantId[$secondGrantId]['id'])->toBe((int)$secondSubuser['id']); expect($byGrantId[$secondGrantId]['customer_name'])->toBe('Fleet Customer Beta'); }); it('lets superusers invite chauffeurs for a selected customer', function (): void { $session = api_fixtures()->createUserSession(['add_subusers']); $customer = api_fixtures()->createUser([ 'display_name' => 'Invite Target Customer', 'economic_customer_name' => 'Invite Target Customer', ]); $phone = 71000000 + ((int)$customer['customer_number'] % 1000000); $createdSubuserId = null; $createdGrantId = null; $setupToken = null; try { $response = api_client()->post('/superuser/subusers/invite', [ 'customer_number' => (int)$customer['customer_number'], 'name' => 'Invited Driver', 'phone_country_code' => 45, 'phone' => $phone, 'permissions' => ['VEHICLES_LIST'], 'note' => 'Created by superuser test', ], $session['headers']); $response ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $payload = $response->data(); $createdSubuserId = isset($payload['subuser']['id']) ? (int)$payload['subuser']['id'] : null; $createdGrantId = isset($payload['grant']['id']) ? (int)$payload['grant']['id'] : null; $setupToken = isset($payload['invite']['setup_token']) ? (string)$payload['invite']['setup_token'] : null; expect($payload['subuser']['customer_number'] ?? null)->toBe((int)$customer['customer_number']); expect($payload['subuser']['name'] ?? null)->toBe('Invited Driver'); expect($payload['subuser']['grant_permissions'] ?? null)->toBe(['VEHICLES_LIST']); expect($payload['grant']['note'] ?? null)->toBe('Created by superuser test'); expect($payload['invite']['setup_link'] ?? null)->toBeString(); } finally { if ($setupToken !== null && $setupToken !== '') { (new \objects\subusers_o())->invalidateSetupToken($setupToken); } if ($createdGrantId !== null) { api_test_runtime()->db()->query('DELETE FROM `subuser_grants` WHERE `id` = ' . $createdGrantId); } if ($createdSubuserId !== null) { api_test_runtime()->db()->query('DELETE FROM `tokens` WHERE `user_id` = ' . $createdSubuserId . " AND `type` = 'AUTH_TOKEN_SUBUSER'"); api_test_runtime()->db()->query('DELETE FROM `subusers` WHERE `id` = ' . $createdSubuserId); } } }); it('applies simplified driver access templates when inviting and updating chauffeur grants', function (): void { api_test_covers('POST /superuser/subusers/invite', 'happy'); api_test_covers('PATCH /superuser/users/{user_id}/subusers/grants/{grant_id}', 'happy'); api_test_covers('PATCH /superuser/users/{user_id}/subusers/grants/{grant_id}', 'failure'); $session = api_fixtures()->createUserSession(['add_subusers', 'manage_subuser_grants']); $customer = api_fixtures()->createUser(['display_name' => 'Template Target Customer']); $phone = 72000000 + ((int)$customer['customer_number'] % 1000000); $createdSubuserId = null; $createdGrantId = null; $setupToken = null; try { $invite = api_client()->post('/superuser/subusers/invite', [ 'customer_number' => (int)$customer['customer_number'], 'name' => 'Template Driver', 'phone_country_code' => 45, 'phone' => $phone, 'permission_template_key' => 'booking_coordinator', ], $session['headers']); $invite ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $payload = $invite->data(); $createdSubuserId = isset($payload['subuser']['id']) ? (int)$payload['subuser']['id'] : null; $createdGrantId = isset($payload['grant']['id']) ? (int)$payload['grant']['id'] : null; $setupToken = isset($payload['invite']['setup_token']) ? (string)$payload['invite']['setup_token'] : null; expect($payload['subuser']['permission_template_key'] ?? null)->toBe('booking_coordinator'); expect($payload['subuser']['grant_permissions'] ?? null) ->toContain('BOOKINGS_EDIT') ->toContain('BOOKINGS_ADD') ->not->toContain('SUBUSERS_ADD'); $deactivate = api_client()->request( 'PATCH', '/superuser/users/' . $customer['id'] . '/subusers/grants/' . $createdGrantId, ['permission_template_key' => 'deactivated'], $session['headers'] ); $deactivate ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($deactivate->data()['subuser']['permission_template_key'] ?? null)->toBe('deactivated'); expect($deactivate->data()['grant']['enabled'] ?? null)->toBeFalse(); expect($deactivate->data()['grant']['permissions'] ?? null)->toBe([]); $invalid = api_client()->request( 'PATCH', '/superuser/users/' . $customer['id'] . '/subusers/grants/' . $createdGrantId, ['permission_template_key' => 'unknown_template'], $session['headers'] ); $invalid ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false); } finally { if ($setupToken !== null && $setupToken !== '') { (new \objects\subusers_o())->invalidateSetupToken($setupToken); } if ($createdGrantId !== null) { api_test_runtime()->db()->query('DELETE FROM `subuser_grants` WHERE `id` = ' . $createdGrantId); } if ($createdSubuserId !== null) { api_test_runtime()->db()->query('DELETE FROM `tokens` WHERE `user_id` = ' . $createdSubuserId . " AND `type` = 'AUTH_TOKEN_SUBUSER'"); api_test_runtime()->db()->query('DELETE FROM `subusers` WHERE `id` = ' . $createdSubuserId); } } }); it('lists and summarizes chauffeurs through the user-scoped superuser route', function (): void { $session = api_fixtures()->createUserSession(['list_subusers']); $targetCustomer = api_fixtures()->createUser([ 'display_name' => 'Scoped Customer Alpha', 'economic_customer_name' => 'Scoped Customer Alpha', ]); $otherCustomer = api_fixtures()->createUser([ 'display_name' => 'Scoped Customer Beta', 'economic_customer_name' => 'Scoped Customer Beta', ]); $activeSubuser = api_fixtures()->createSubuser(['name' => 'Scoped Active Driver']); $pendingSubuser = api_fixtures()->createSubuser([ 'name' => 'Scoped Pending Driver', 'password_plaintext' => null, ]); $disabledSubuser = api_fixtures()->createSubuser(['name' => 'Scoped Disabled Driver']); $otherSubuser = api_fixtures()->createSubuser(['name' => 'Scoped Other Driver']); $activeGrantId = api_fixtures()->grantSubuser( (int)$activeSubuser['id'], (int)$targetCustomer['customer_number'], ['VEHICLES_LIST'] ); api_fixtures()->grantSubuser( (int)$pendingSubuser['id'], (int)$targetCustomer['customer_number'], ['BOOKINGS_LIST'] ); $disabledGrantId = api_fixtures()->grantSubuser( (int)$disabledSubuser['id'], (int)$targetCustomer['customer_number'], ['ORDERS_LIST'] ); api_test_runtime()->db()->query('UPDATE `subuser_grants` SET `enabled` = 0 WHERE `id` = ' . $disabledGrantId); api_fixtures()->grantSubuser( (int)$otherSubuser['id'], (int)$otherCustomer['customer_number'], ['SELFSERVE_LIST'] ); $response = api_client()->get( '/superuser/users/' . $targetCustomer['id'] . '/subusers?page=1&limit=20&include_non_enabled=true', $session['headers'] ); $summary = api_client()->get( '/superuser/users/' . $targetCustomer['id'] . '/subusers/summary', $session['headers'] ); $response ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $summary ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $rows = is_array($response->data()) ? $response->data() : []; $grantIds = array_map(static fn (array $row): int => (int)($row['grant_id'] ?? 0), $rows); expect($grantIds)->toContain($activeGrantId); expect($grantIds)->not->toContain(0); expect($rows)->toHaveCount(3); foreach ($rows as $row) { expect($row['customer_number'] ?? null)->toBe((int)$targetCustomer['customer_number']); } expect($response->meta()['user_context']['user_id'] ?? null)->toBe((int)$targetCustomer['id']); expect($response->meta()['subusers_summary'] ?? null)->toMatchArray([ 'total' => 3, 'active' => 1, 'pending_setup' => 1, 'disabled' => 1, ]); expect($summary->data())->toMatchArray([ 'total' => 3, 'active' => 1, 'pending_setup' => 1, 'disabled' => 1, ]); }); it('edits only customer-matching chauffeur grants through the user-scoped route', function (): void { $session = api_fixtures()->createUserSession(['manage_subuser_grants']); $targetCustomer = api_fixtures()->createUser(['display_name' => 'Scoped Patch Customer']); $otherCustomer = api_fixtures()->createUser(['display_name' => 'Scoped Patch Other']); $targetSubuser = api_fixtures()->createSubuser(['name' => 'Patch Target Driver']); $otherSubuser = api_fixtures()->createSubuser(['name' => 'Patch Other Driver']); $targetVehicle = api_fixtures()->createVehicle([ 'customer_id' => (int)$targetCustomer['customer_number'], 'type' => 1, 'reg' => 'scope123', ]); $otherVehicle = api_fixtures()->createVehicle([ 'customer_id' => (int)$otherCustomer['customer_number'], 'type' => 1, 'reg' => 'other123', ]); $targetGrantId = api_fixtures()->grantSubuser( (int)$targetSubuser['id'], (int)$targetCustomer['customer_number'], ['VEHICLES_LIST'] ); $otherGrantId = api_fixtures()->grantSubuser( (int)$otherSubuser['id'], (int)$otherCustomer['customer_number'], ['BOOKINGS_LIST'] ); $update = api_client()->request( 'PATCH', '/superuser/users/' . $targetCustomer['id'] . '/subusers/grants/' . $targetGrantId, [ 'enabled' => false, 'note' => 'Scoped note', 'permissions' => ['ORDERS_LIST'], 'assigned_vehicle_id' => (int)$targetVehicle['id'], ], $session['headers'] ); $mismatchedVehicle = api_client()->request( 'PATCH', '/superuser/users/' . $targetCustomer['id'] . '/subusers/grants/' . $targetGrantId, ['assigned_vehicle_id' => (int)$otherVehicle['id']], $session['headers'] ); $crossCustomer = api_client()->request( 'PATCH', '/superuser/users/' . $targetCustomer['id'] . '/subusers/grants/' . $otherGrantId, ['note' => 'Should not save'], $session['headers'] ); $update ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $mismatchedVehicle ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false); $crossCustomer ->assertStatus(404) ->assertEnvelope() ->assertSuccess(false); expect($update->data()['grant']['enabled'] ?? null)->toBeFalse(); expect($update->data()['grant']['note'] ?? null)->toBe('Scoped note'); expect($update->data()['grant']['permissions'] ?? null)->toBe(['ORDERS_LIST']); expect($update->data()['grant']['assigned_vehicle_id'] ?? null)->toBe((int)$targetVehicle['id']); expect($update->data()['subuser']['assigned_vehicle_reg'] ?? null)->toBe('SCOPE123'); }); it('lets superusers edit chauffeur account details and set passwords', function (): void { api_test_covers('PATCH /superuser/subusers/{subuser_id}', 'happy'); api_test_covers('POST /superuser/subusers/{subuser_id}/password', 'happy'); $session = api_fixtures()->createUserSession(['edit_subusers']); $subuser = api_fixtures()->createSubuser([ 'password_plaintext' => null, 'name' => 'Admin Managed Driver', 'email' => null, 'phone_country_code' => 45, 'phone' => 73123456, ]); $subuserObject = (new \objects\subusers_o())->select((int)$subuser['id']); $subuserObject->getObjectProperties(); $setupToken = $subuserObject->generateSetupToken(); try { $profile = api_client()->request( 'PATCH', '/superuser/subusers/' . $subuser['id'], [ 'name' => 'Admin Updated Driver', 'email' => 'admin.updated.driver@example.com', 'phone_country_code' => 46, 'phone' => 73123457, ], $session['headers'] ); $password = api_client()->post( '/superuser/subusers/' . $subuser['id'] . '/password', ['password' => 'ValidPass123'], $session['headers'] ); $profile ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $password ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($profile->data()['subuser']['name'] ?? null)->toBe('Admin Updated Driver'); expect($profile->data()['subuser']['email'] ?? null)->toBe('admin.updated.driver@example.com'); expect($profile->data()['subuser']['phone_country_code'] ?? null)->toBe(46); expect($profile->data()['subuser']['phone'] ?? null)->toBe(73123457); expect($password->data()['subuser']['setup_required'] ?? null)->toBeFalse(); expect((new \objects\subusers_o())->getSubuserBySetupToken($setupToken))->toBeNull(); $row = api_fixtures()->fetchRowById('subusers', (int)$subuser['id']); expect(password_verify('ValidPass123', (string)($row['password'] ?? '')))->toBeTrue(); } finally { (new \objects\subusers_o())->invalidateSetupToken($setupToken); } }); it('creates direct chauffeur login links scoped to a selected customer grant', function (): void { api_test_covers('POST /superuser/subusers/{subuser_id}/login-link', 'happy'); api_test_covers('POST /superuser/subusers/{subuser_id}/login-link', 'failure'); $session = api_fixtures()->createUserSession(['edit_subusers', 'SUPERUSER_INTIMIDATE']); $firstCustomer = api_fixtures()->createUser(['display_name' => 'Direct Login Customer Alpha']); $secondCustomer = api_fixtures()->createUser(['display_name' => 'Direct Login Customer Beta']); $subuser = api_fixtures()->createSubuser(['name' => 'Direct Login Driver']); api_fixtures()->grantSubuser( (int)$subuser['id'], (int)$firstCustomer['customer_number'], ['VEHICLES_LIST'] ); $secondGrantId = api_fixtures()->grantSubuser( (int)$subuser['id'], (int)$secondCustomer['customer_number'], ['ORDERS_LIST'] ); $ambiguous = api_client()->post( '/superuser/subusers/' . $subuser['id'] . '/login-link', [], $session['headers'] ); $direct = api_client()->post( '/superuser/subusers/' . $subuser['id'] . '/login-link', ['grant_id' => $secondGrantId], $session['headers'] ); $ambiguous ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false) ->assertMessage('Customer number or grant id is required for drivers with multiple customer grants'); $direct ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); expect($direct->data()['customer_number'] ?? null)->toBe((int)$secondCustomer['customer_number']); expect($direct->data()['login_path'] ?? null)->toBeString(); $parts = parse_url((string)$direct->data()['login_path']); parse_str((string)($parts['query'] ?? ''), $query); expect($parts['path'] ?? null)->toBe('/login/qr'); expect($query['type'] ?? null)->toBe('subuser'); expect((int)($query['customer_number'] ?? 0))->toBe((int)$secondCustomer['customer_number']); expect($query['token'] ?? null)->toBeString(); $resolved = (new \objects\subusers_o())->getSubuserBySessionToken((string)$query['token']); expect($resolved)->not->toBeNull(); expect((int)$resolved->id)->toBe((int)$subuser['id']); (new \objects\subusers_o())->invalidateSessionToken((string)$query['token']); }); it('rejects mismatched customer numbers on user-scoped chauffeur invites', function (): void { $session = api_fixtures()->createUserSession(['add_subusers']); $targetCustomer = api_fixtures()->createUser(['display_name' => 'Scoped Invite Customer']); $otherCustomer = api_fixtures()->createUser(['display_name' => 'Scoped Invite Other']); $response = api_client()->post('/superuser/users/' . $targetCustomer['id'] . '/subusers/invite', [ 'customer_number' => (int)$otherCustomer['customer_number'], 'name' => 'Mismatched Driver', 'phone_country_code' => 45, 'phone' => 71999999, ], $session['headers']); $response ->assertStatus(400) ->assertEnvelope() ->assertSuccess(false) ->assertMessage('Customer number does not match selected user'); }); it('resends pending user-scoped chauffeur invites and blocks accepted accounts', function (): void { $session = api_fixtures()->createUserSession(['edit_subusers']); $targetCustomer = api_fixtures()->createUser(['display_name' => 'Scoped Resend Customer']); $pendingSubuser = api_fixtures()->createSubuser([ 'name' => 'Pending Resend Driver', 'password_plaintext' => null, ]); $acceptedSubuser = api_fixtures()->createSubuser(['name' => 'Accepted Resend Driver']); api_fixtures()->grantSubuser( (int)$pendingSubuser['id'], (int)$targetCustomer['customer_number'], ['VEHICLES_LIST'] ); api_fixtures()->grantSubuser( (int)$acceptedSubuser['id'], (int)$targetCustomer['customer_number'], ['VEHICLES_LIST'] ); $pending = api_client()->post( '/superuser/users/' . $targetCustomer['id'] . '/subusers/' . $pendingSubuser['id'] . '/invite/resend', [], $session['headers'] ); $accepted = api_client()->post( '/superuser/users/' . $targetCustomer['id'] . '/subusers/' . $acceptedSubuser['id'] . '/invite/resend', [], $session['headers'] ); $pending ->assertStatus(200) ->assertEnvelope() ->assertSuccess(); $accepted ->assertStatus(409) ->assertEnvelope() ->assertSuccess(false); $token = $pending->data()['invite']['setup_token'] ?? null; expect($token)->toBeString(); (new \objects\subusers_o())->invalidateSetupToken((string)$token); });