Files
api/services/nginx/app/tests/Unit/Subusers/SubuserContactVerificationServiceTest.php
T

149 lines
5.4 KiB
PHP

<?php
use classes\subuser_contact_verification_service;
use objects\subusers_o;
function subuser_contact_verification_test_subuser(array $values = []): subusers_o
{
$subuser = new subusers_o();
$subuser->id = -1;
$subuser->getObjectProperties();
$defaults = [
'name' => 'Test Driver',
'email' => 'driver@example.com',
'email_verified_at' => null,
'phone_country_code' => 45,
'phone' => 12345678,
'phone_verified_at' => null,
];
foreach ([...$defaults, ...$values] as $property => $value) {
$subuser->{$property}->set($value);
}
return $subuser;
}
function subuser_contact_verification_test_redis(): object
{
return new class {
public array $values = [];
public array $ttls = [];
public function get(string $key): ?string
{
return $this->values[$key] ?? null;
}
public function setEx(string $key, string $value, int $expiration): self
{
$this->values[$key] = $value;
$this->ttls[$key] = $expiration;
return $this;
}
public function delete(string $key): self
{
unset($this->values[$key], $this->ttls[$key]);
return $this;
}
};
}
it('sends and verifies a subuser email verification code without storing plaintext codes', function (): void {
$redis = subuser_contact_verification_test_redis();
$sentEmails = [];
$now = 1_700_000_000;
$subuser = subuser_contact_verification_test_subuser();
$service = new subuser_contact_verification_service(
redis: $redis,
codeGenerator: fn () => '123456',
timeProvider: fn () => $now,
emailSender: function (string $destination, string $code) use (&$sentEmails): void {
$sentEmails[] = compact('destination', 'code');
}
);
$delivery = $service->sendCode($subuser, 'email', ['type' => 'test', 'id' => 42]);
expect($delivery['status'])->toBe('sent')
->and($delivery['expires_in'])->toBe(subuser_contact_verification_service::CODE_TTL_SECONDS)
->and($sentEmails)->toBe([
[
'destination' => 'driver@example.com',
'code' => '123456',
],
])
->and($redis->values)->toHaveCount(1);
$storedChallenge = json_decode((string)array_values($redis->values)[0], true);
expect($storedChallenge['hash'])->not->toBe('123456')
->and($storedChallenge['attempts'])->toBe(0);
$wrong = $service->verifyCode($subuser, 'email', '000000', ['type' => 'test', 'id' => 42]);
expect($wrong['status'])->toBe('invalid_code');
$updatedChallenge = json_decode((string)array_values($redis->values)[0], true);
expect($updatedChallenge['attempts'])->toBe(1);
$verified = $service->verifyCode($subuser, 'email', '123456', ['type' => 'test', 'id' => 42]);
expect($verified['status'])->toBe('verified')
->and($subuser->email_verified_at->value())->toBe(date('Y-m-d H:i:s', $now))
->and($redis->values)->toBe([]);
$status = $service->status($subuser);
expect($status['email']['verified'])->toBeTrue()
->and($status['phone']['verified'])->toBeFalse()
->and($status['state'])->toBe('partial');
});
it('clears verification timestamps when subuser contact values change', function (): void {
$subuser = subuser_contact_verification_test_subuser([
'email_verified_at' => '2026-07-13 10:00:00',
'phone_verified_at' => '2026-07-13 10:00:00',
]);
$service = new subuser_contact_verification_service(redis: subuser_contact_verification_test_redis());
$emailUpdate = $service->clearVerificationForChangedContacts($subuser, [
'email' => 'new-driver@example.com',
'phone' => 12345678,
]);
expect($emailUpdate['email_verified_at'])->toBeNull()
->and($emailUpdate)->not->toHaveKey('phone_verified_at');
$phoneUpdate = $service->clearVerificationForChangedContacts($subuser, [
'phone_country_code' => 46,
]);
expect($phoneUpdate['phone_verified_at'])->toBeNull()
->and($phoneUpdate)->not->toHaveKey('email_verified_at');
});
it('lets superusers set and clear contact verification state', function (): void {
$now = 1_800_000_000;
$subuser = subuser_contact_verification_test_subuser();
$service = new subuser_contact_verification_service(
redis: subuser_contact_verification_test_redis(),
timeProvider: fn () => $now
);
$verified = $service->setVerificationState($subuser, 'email', true, ['type' => 'user', 'id' => 99]);
expect($verified['status'])->toBe('verified')
->and($subuser->email_verified_at->value())->toBe(date('Y-m-d H:i:s', $now));
$unverified = $service->setVerificationState($subuser, 'email', false, ['type' => 'user', 'id' => 99]);
expect($unverified['status'])->toBe('unverified')
->and($subuser->email_verified_at->value())->toBeNull();
});
it('refuses to mark missing contact values as verified', function (): void {
$subuser = subuser_contact_verification_test_subuser([
'email' => null,
]);
$service = new subuser_contact_verification_service(redis: subuser_contact_verification_test_redis());
$result = $service->setVerificationState($subuser, 'email', true, ['type' => 'user', 'id' => 99]);
expect($result['status'])->toBe('missing_destination')
->and($subuser->email_verified_at->value())->toBeNull();
});