Files
shopPRO/tests/Unit/Domain/User/UserRepositoryTest.php

407 lines
14 KiB
PHP

<?php
namespace Tests\Unit\Domain\User;
use PHPUnit\Framework\TestCase;
use Domain\User\UserRepository;
class UserRepositoryTest extends TestCase
{
public function testFindReturnsUserWhenExists(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_users', '*', ['id' => 7])
->willReturn(['id' => 7, 'login' => 'admin']);
$repository = new UserRepository($mockDb);
$user = $repository->find(7);
$this->assertIsArray($user);
$this->assertSame(7, (int)$user['id']);
$this->assertSame('admin', $user['login']);
}
public function testFindReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(false);
$repository = new UserRepository($mockDb);
$this->assertNull($repository->find(123));
}
public function testCheckLoginReturnsErrorWhenLoginIsTaken(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_users', 'login', [
'AND' => [
'login' => 'taken@example.com',
'id[!]' => 10,
],
])
->willReturn('taken@example.com');
$repository = new UserRepository($mockDb);
$response = $repository->checkLogin('taken@example.com', 10);
$this->assertSame('error', $response['status']);
}
public function testCheckLoginReturnsOkWhenAvailable(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(null);
$repository = new UserRepository($mockDb);
$response = $repository->checkLogin('free@example.com', 5);
$this->assertSame('ok', $response['status']);
}
public function testSaveReturnsErrorForTooShortPasswordOnCreate(): void
{
$mockDb = $this->createMock(\medoo::class);
$repository = new UserRepository($mockDb);
$response = $repository->save(0, 'admin@example.com', 'on', '1234', '1234', 1, 'on', 'admin@example.com');
$this->assertSame('error', $response['status']);
}
public function testSaveReturnsErrorForMismatchedPasswordsOnCreate(): void
{
$mockDb = $this->createMock(\medoo::class);
$repository = new UserRepository($mockDb);
$response = $repository->save(0, 'admin@example.com', 'on', 'password1', 'password2', 1);
$this->assertSame('error', $response['status']);
$this->assertStringContainsString('rozne', $response['msg']);
}
public function testSaveCreatesUserWithNormalizedSwitches(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('insert')
->with('pp_users', $this->callback(function (array $row): bool {
return $row['login'] === 'new@example.com'
&& $row['status'] === 1
&& $row['admin'] === 1
&& $row['twofa_enabled'] === 1
&& $row['twofa_email'] === '2fa@example.com'
&& $row['password'] === md5('secret5');
}))
->willReturn($this->createMock(\PDOStatement::class));
$repository = new UserRepository($mockDb);
$response = $repository->save(0, 'new@example.com', 'on', 'secret5', 'secret5', 1, 'on', '2fa@example.com');
$this->assertSame('ok', $response['status']);
}
public function testSaveUpdatesExistingUserWithPassword(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('update')
->withConsecutive(
[
'pp_users',
$this->callback(fn(array $d) => $d['password'] === md5('newpass5')),
['id' => 5],
],
[
'pp_users',
$this->callback(fn(array $d) => $d['login'] === 'user@example.com' && $d['status'] === 1),
['id' => 5],
]
);
$repository = new UserRepository($mockDb);
$response = $repository->save(5, 'user@example.com', 'on', 'newpass5', 'newpass5', 1);
$this->assertSame('ok', $response['status']);
}
public function testSaveUpdatesExistingUserWithoutPassword(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('update')
->with(
'pp_users',
$this->callback(fn(array $d) => $d['login'] === 'user@example.com'),
['id' => 5]
);
$repository = new UserRepository($mockDb);
$response = $repository->save(5, 'user@example.com', 'on', '', '', 1);
$this->assertSame('ok', $response['status']);
}
public function testSaveReturnsErrorForTooShortPasswordOnUpdate(): void
{
$mockDb = $this->createMock(\medoo::class);
$repository = new UserRepository($mockDb);
$response = $repository->save(5, 'user@example.com', 1, '123', '123', 1);
$this->assertSame('error', $response['status']);
}
public function testSaveReturnsErrorForMismatchedPasswordsOnUpdate(): void
{
$mockDb = $this->createMock(\medoo::class);
$repository = new UserRepository($mockDb);
$response = $repository->save(5, 'user@example.com', 1, 'password1', 'password2', 1);
$this->assertSame('error', $response['status']);
}
public function testDeleteReturnsTrue(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('delete')
->with('pp_users', ['id' => 3])
->willReturn($this->createMock(\PDOStatement::class));
$repository = new UserRepository($mockDb);
$this->assertTrue($repository->delete(3));
}
public function testDeleteReturnsFalseOnFailure(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('delete')->willReturn(false);
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->delete(999));
}
public function testDetailsReturnsUserByLogin(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_users', '*', ['login' => 'admin@shop.com'])
->willReturn(['id' => 1, 'login' => 'admin@shop.com']);
$repository = new UserRepository($mockDb);
$user = $repository->details('admin@shop.com');
$this->assertIsArray($user);
$this->assertSame('admin@shop.com', $user['login']);
}
public function testDetailsReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(false);
$repository = new UserRepository($mockDb);
$this->assertNull($repository->details('nonexistent@shop.com'));
}
public function testLogonReturnsSuccessForValidCredentials(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(3))
->method('get')
->withConsecutive(
['pp_users', '*', ['login' => 'admin@example.com']],
['pp_users', '*', ['AND' => ['login' => 'admin@example.com', 'status' => 1, 'error_logged_count[<]' => 5]]],
['pp_users', '*', ['AND' => ['login' => 'admin@example.com', 'status' => 1, 'password' => md5('password123')]]]
)
->willReturnOnConsecutiveCalls(
['id' => 5],
['id' => 5],
['id' => 5]
);
$mockDb->expects($this->once())
->method('update')
->with(
'pp_users',
$this->callback(function (array $data): bool {
return isset($data['last_logged']) && $data['error_logged_count'] === 0;
}),
['login' => 'admin@example.com']
);
$repository = new UserRepository($mockDb);
$result = $repository->logon('admin@example.com', 'password123');
$this->assertSame(1, $result);
}
public function testLogonReturnsZeroForNonexistentUser(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_users', '*', ['login' => 'noone@example.com'])
->willReturn(false);
$repository = new UserRepository($mockDb);
$this->assertSame(0, $repository->logon('noone@example.com', 'pass'));
}
public function testLogonReturnsNegativeOneForBlockedUser(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('get')
->withConsecutive(
['pp_users', '*', ['login' => 'blocked@example.com']],
['pp_users', '*', ['AND' => ['login' => 'blocked@example.com', 'status' => 1, 'error_logged_count[<]' => 5]]]
)
->willReturnOnConsecutiveCalls(
['id' => 3, 'status' => 0],
false
);
$repository = new UserRepository($mockDb);
$this->assertSame(-1, $repository->logon('blocked@example.com', 'pass'));
}
public function testVerifyTwofaCodeReturnsFalseForNonexistentUser(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(false);
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->verifyTwofaCode(999, '123456'));
}
public function testVerifyTwofaCodeReturnsFalseAfterMaxAttempts(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn([
'id' => 1,
'twofa_failed_attempts' => 5,
'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT),
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 300),
]);
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->verifyTwofaCode(1, '123456'));
}
public function testVerifyTwofaCodeReturnsFalseForExpiredCode(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn([
'id' => 1,
'twofa_failed_attempts' => 0,
'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT),
'twofa_expires_at' => date('Y-m-d H:i:s', time() - 60),
]);
$mockDb->expects($this->once())->method('update');
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->verifyTwofaCode(1, '123456'));
}
public function testVerifyTwofaCodeReturnsTrueForValidCode(): void
{
$code = '654321';
$hash = password_hash($code, PASSWORD_DEFAULT);
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn([
'id' => 1,
'twofa_failed_attempts' => 0,
'twofa_code_hash' => $hash,
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 300),
]);
$mockDb->expects($this->once())
->method('update')
->with('pp_users', $this->callback(function (array $d): bool {
return $d['twofa_code_hash'] === null
&& $d['twofa_expires_at'] === null
&& $d['twofa_failed_attempts'] === 0;
}), ['id' => 1]);
$repository = new UserRepository($mockDb);
$this->assertTrue($repository->verifyTwofaCode(1, $code));
}
public function testSendTwofaCodeReturnsFalseWhen2FADisabled(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn([
'id' => 1,
'twofa_enabled' => 0,
'login' => 'test@example.com',
]);
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->sendTwofaCode(1));
}
public function testSendTwofaCodeReturnsFalseForInvalidEmail(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn([
'id' => 1,
'twofa_enabled' => 1,
'twofa_email' => 'not-an-email',
'login' => 'also-not-email',
]);
$repository = new UserRepository($mockDb);
$this->assertFalse($repository->sendTwofaCode(1));
}
public function testUpdateByIdCallsDbUpdate(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('update')
->with('pp_users', ['status' => 0], ['id' => 7])
->willReturn($this->createMock(\PDOStatement::class));
$repository = new UserRepository($mockDb);
$this->assertTrue($repository->updateById(7, ['status' => 0]));
}
public function testListForAdminReturnsItemsAndTotal(): void
{
$mockDb = $this->createMock(\medoo::class);
$countStmt = $this->createMock(\PDOStatement::class);
$countStmt->expects($this->once())
->method('fetchAll')
->willReturn([[2]]);
$dataStmt = $this->createMock(\PDOStatement::class);
$dataStmt->expects($this->once())
->method('fetchAll')
->willReturn([
['id' => 2, 'login' => 'a@example.com', 'status' => 1],
['id' => 3, 'login' => 'b@example.com', 'status' => 0],
]);
$mockDb->expects($this->exactly(2))
->method('query')
->willReturnOnConsecutiveCalls($countStmt, $dataStmt);
$repository = new UserRepository($mockDb);
$result = $repository->listForAdmin(['login' => '', 'status' => ''], 'login', 'ASC', 1, 15);
$this->assertSame(2, $result['total']);
$this->assertCount(2, $result['items']);
$this->assertSame('a@example.com', $result['items'][0]['login']);
}
}