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']); } }