refactor users module to domain/controller and release 0.253 update package
This commit is contained in:
406
tests/Unit/Domain/User/UserRepositoryTest.php
Normal file
406
tests/Unit/Domain/User/UserRepositoryTest.php
Normal file
@@ -0,0 +1,406 @@
|
||||
<?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']);
|
||||
}
|
||||
}
|
||||
131
tests/Unit/admin/Controllers/UsersControllerTest.php
Normal file
131
tests/Unit/admin/Controllers/UsersControllerTest.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\UsersController;
|
||||
use Domain\User\UserRepository;
|
||||
|
||||
class UsersControllerTest extends TestCase
|
||||
{
|
||||
private $mockRepository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->mockRepository = $this->createMock(UserRepository::class);
|
||||
$this->controller = new UsersController($this->mockRepository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepository(): void
|
||||
{
|
||||
$controller = new UsersController($this->mockRepository);
|
||||
$this->assertInstanceOf(UsersController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasViewListMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'view_list'));
|
||||
}
|
||||
|
||||
public function testHasUserEditMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'user_edit'));
|
||||
}
|
||||
|
||||
public function testHasUserSaveMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'user_save'));
|
||||
}
|
||||
|
||||
public function testHasUserDeleteMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'user_delete'));
|
||||
}
|
||||
|
||||
public function testHasTwofaMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'twofa'));
|
||||
}
|
||||
|
||||
public function testHasLoginFormMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'login_form'));
|
||||
}
|
||||
|
||||
public function testActionMethodReturnTypes(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('user_edit')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('login_form')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('twofa')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('user_save')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('user_delete')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresUserRepository(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(UsersController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertEquals('Domain\User\UserRepository', $params[0]->getType()->getName());
|
||||
}
|
||||
|
||||
public function testNormalizeUserReturnsDefaultsForNull(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$method = $reflection->getMethod('normalizeUser');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->controller, null);
|
||||
|
||||
$this->assertSame(0, $result['id']);
|
||||
$this->assertSame('', $result['login']);
|
||||
$this->assertSame(1, $result['status']);
|
||||
$this->assertSame(1, $result['admin']);
|
||||
$this->assertSame(0, $result['twofa_enabled']);
|
||||
$this->assertSame('', $result['twofa_email']);
|
||||
}
|
||||
|
||||
public function testNormalizeUserCastsTypes(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$method = $reflection->getMethod('normalizeUser');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->controller, [
|
||||
'id' => '15',
|
||||
'login' => 'admin@test.com',
|
||||
'status' => '0',
|
||||
'admin' => '1',
|
||||
'twofa_enabled' => '1',
|
||||
'twofa_email' => 'twofa@test.com',
|
||||
]);
|
||||
|
||||
$this->assertSame(15, $result['id']);
|
||||
$this->assertSame('admin@test.com', $result['login']);
|
||||
$this->assertSame(0, $result['status']);
|
||||
$this->assertSame(1, $result['admin']);
|
||||
$this->assertSame(1, $result['twofa_enabled']);
|
||||
$this->assertSame('twofa@test.com', $result['twofa_email']);
|
||||
}
|
||||
|
||||
public function testNormalizeUserHandlesPartialData(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$method = $reflection->getMethod('normalizeUser');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($this->controller, ['id' => 3, 'login' => 'partial@test.com']);
|
||||
|
||||
$this->assertSame(3, $result['id']);
|
||||
$this->assertSame('partial@test.com', $result['login']);
|
||||
$this->assertSame(1, $result['status']);
|
||||
$this->assertSame(1, $result['admin']);
|
||||
$this->assertSame(0, $result['twofa_enabled']);
|
||||
$this->assertSame('', $result['twofa_email']);
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ if (!class_exists('S')) {
|
||||
public static function set_message($msg) {}
|
||||
public static function clear_redis_cache() {}
|
||||
public static function clear_product_cache($id) {}
|
||||
public static function send_email($to, $subject, $body) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user