DOGNVASK-OP 4: when a new driver registers via the public QR-code endpoint, the customer (dispatcher) is now notified by SMS with approve/deny links the moment the driver is created — no longer only after the driver completes their SMS setup. - registerPublicSubuser() now calls seedPendingGrantAndNotifyDispatcher(), which gets-or-creates the pending company grant, issues grant_approve / grant_deny action tokens, and SMSes the customer with the same link structure the existing /subusers/access-decision flow already consumes. - seedPendingGrantAndNotifyDispatcher() is idempotent: if a previous request is still live (unconsumed, unexpired tokens outstanding), the SMS is skipped so a driver re-scanning the QR code does not spam the dispatcher. - /subusers/setup completion reuses the seeded grant and only re-pings the dispatcher when no decision tokens are outstanding, so a fresh flow (e.g. legacy scans) still notifies once. - Public response stays uniform (no setup_token / customer_number leaked), per existing contract. - New contract tests assert the new code paths, helper methods, and duplicate-guard semantics.
90 lines
5.1 KiB
PHP
90 lines
5.1 KiB
PHP
<?php
|
|
|
|
function public_subuser_registration_method(): string
|
|
{
|
|
$code = (string)file_get_contents(app_path('routes/subusersRoute.php'));
|
|
$start = strpos($code, 'private function registerPublicSubuser(): void');
|
|
$end = strpos($code, 'public function run(): void', $start === false ? 0 : $start);
|
|
|
|
if ($start === false || $end === false || $end <= $start) {
|
|
throw new RuntimeException('Unable to locate the public subuser registration handler.');
|
|
}
|
|
|
|
return substr($code, $start, $end - $start);
|
|
}
|
|
|
|
it('routes both public driver registration aliases through one canonical handler', function (): void {
|
|
$code = (string)file_get_contents(app_path('routes/subusersRoute.php'));
|
|
$normalized = preg_replace('/\s+/', ' ', $code);
|
|
|
|
expect($normalized)->toContain("\$this->post('/subusers', function () { \$this->registerPublicSubuser(); });");
|
|
expect($normalized)->toContain("\$this->post('/subusers/me', function () { \$this->registerPublicSubuser(); });");
|
|
expect(substr_count($normalized, '$this->registerPublicSubuser();'))->toBe(2);
|
|
});
|
|
|
|
it('requires abuse controls and serializes idempotent driver registration writes', function (): void {
|
|
$method = preg_replace('/\s+/', ' ', public_subuser_registration_method());
|
|
|
|
expect($method)->toContain('$this->requireRecaptcha();');
|
|
expect($method)->toContain("'public_registration_ip'");
|
|
expect($method)->toContain("'public_registration_identity'");
|
|
expect($method)->toContain("3, 60 * 60, false");
|
|
expect($method)->toContain('SELECT GET_LOCK(?, 5)');
|
|
expect($method)->toContain('$db->conn->begin_transaction();');
|
|
expect($method)->toContain('$db->conn->commit();');
|
|
expect($method)->toContain('SELECT RELEASE_LOCK(?)');
|
|
expect(strpos($method, '$db->conn->commit();'))->toBeLessThan(strpos($method, '$this->issueSetupInvite($subuser);'));
|
|
expect($method)->toContain('$this->storePublicRegistrationPending($setupToken, $customerNumber);');
|
|
// TRU-88: dispatcher is notified at driver-creation time, so the public
|
|
// registration now seeds a pending company grant via the helper.
|
|
expect($method)->toContain('$this->seedPendingGrantAndNotifyDispatcher(');
|
|
expect($method)->toContain('$this->hasOutstandingGrantDecisionTokens(');
|
|
});
|
|
|
|
it('seeds the pending grant and notifies the dispatcher on QR driver creation (TRU-88)', function (): void {
|
|
$method = preg_replace('/\s+/', ' ', public_subuser_registration_method());
|
|
|
|
expect($method)->toContain('seedPendingGrantAndNotifyDispatcher');
|
|
expect($method)->toContain('hasOutstandingGrantDecisionTokens');
|
|
expect($method)->toContain('notifyCustomerOfGrantRequest');
|
|
// The dispatcher ping must run BEFORE we store the setup token in Redis
|
|
// so a pre-approval is valid by the time the driver opens their setup link.
|
|
expect(strpos($method, '$this->seedPendingGrantAndNotifyDispatcher('))->toBeLessThan(
|
|
strpos($method, '$this->storePublicRegistrationPending(')
|
|
);
|
|
});
|
|
|
|
it('never returns setup credentials from either public driver registration alias', function (): void {
|
|
$method = public_subuser_registration_method();
|
|
|
|
expect($method)->not->toContain("'setup_link'");
|
|
expect($method)->toContain("'message' => 'If the driver can be registered, setup instructions have been sent.'");
|
|
expect($method)->not->toContain("'customer_number'");
|
|
expect($method)->not->toContain("'already_registered'");
|
|
|
|
$openApi = (string)file_get_contents(app_path('openapi.yaml'));
|
|
$start = strpos($openApi, ' /subusers/me:');
|
|
$end = strpos($openApi, ' /subusers/me/verification:', $start === false ? 0 : $start);
|
|
$operation = $start === false || $end === false ? '' : substr($openApi, $start, $end - $start);
|
|
expect($operation)->toContain('- g_recaptcha_response');
|
|
expect($operation)->not->toContain('setup_token:');
|
|
expect($operation)->not->toContain('setup_link:');
|
|
});
|
|
|
|
it('reuses the pre-seeded grant at setup completion and re-notifies only when no decision tokens are outstanding', function (): void {
|
|
$code = preg_replace('/\s+/', ' ', (string)file_get_contents(app_path('routes/subusersRoute.php')));
|
|
$setupStart = strpos($code, "\$this->post('/subusers/setup', function () {");
|
|
$setupEnd = strpos($code, "\$this->post('/subusers/password-reset/request'", $setupStart === false ? 0 : $setupStart);
|
|
$setup = $setupStart === false || $setupEnd === false ? '' : substr($code, $setupStart, $setupEnd - $setupStart);
|
|
|
|
expect($setup)->toContain('$this->getPublicRegistrationPending($token)');
|
|
expect($setup)->toContain('(new subuser_grants_o())->add(');
|
|
expect($setup)->toContain('$this->notifyCustomerOfGrantRequest(');
|
|
expect($setup)->toContain('$this->clearPublicRegistrationPending($token)');
|
|
// TRU-88: the setup completion must short-circuit the dispatcher SMS
|
|
// when approval/deny tokens were already issued at driver creation time.
|
|
expect($setup)->toContain('$this->hasOutstandingGrantDecisionTokens(');
|
|
expect($setup)->toContain('$this->clearThrottleAttempt($setupThrottleKey);');
|
|
expect(strpos($setup, '$subuser->update('))->toBeLessThan(strpos($setup, '(new subuser_grants_o())->add('));
|
|
});
|